mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Initial Commit 🎉
This commit is contained in:
		
							
								
								
									
										0
									
								
								esphomeyaml/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphomeyaml/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										306
									
								
								esphomeyaml/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								esphomeyaml/__main__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,306 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import argparse | ||||
| import logging | ||||
| import os | ||||
| import random | ||||
| import sys | ||||
|  | ||||
| from esphomeyaml import helpers, mqtt, writer, yaml_util, wizard | ||||
| from esphomeyaml.config import add_component_task, read_config | ||||
| from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_HOSTNAME, CONF_MANUAL_IP, CONF_NAME, \ | ||||
|     CONF_STATIC_IP, \ | ||||
|     CONF_WIFI, CONF_LOGGER, CONF_BAUD_RATE | ||||
| from esphomeyaml.helpers import AssignmentExpression, RawStatement, _EXPRESSIONS, add, \ | ||||
|     get_variable, indent, quote, statement | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| PRE_INITIALIZE = ['esphomeyaml', 'logger', 'wifi', 'ota', 'mqtt', 'i2c'] | ||||
|  | ||||
| CONFIG_PATH = None | ||||
|  | ||||
|  | ||||
| def get_name(config): | ||||
|     return config[CONF_ESPHOMEYAML][CONF_NAME] | ||||
|  | ||||
|  | ||||
| def get_base_path(config): | ||||
|     return os.path.join(os.path.dirname(CONFIG_PATH), get_name(config)) | ||||
|  | ||||
|  | ||||
| def discover_serial_ports(): | ||||
|     # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py | ||||
|     try: | ||||
|         from serial.tools.list_ports import comports | ||||
|     except ImportError: | ||||
|         return None | ||||
|  | ||||
|     result = None | ||||
|     for p, d, h in comports(): | ||||
|         if not p: | ||||
|             continue | ||||
|         if "VID:PID" in h: | ||||
|             if result is not None: | ||||
|                 return None | ||||
|             result = p | ||||
|  | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def run_platformio(*cmd): | ||||
|     def mock_exit(rc): | ||||
|         raise SystemExit(rc) | ||||
|  | ||||
|     orig_argv = sys.argv | ||||
|     orig_exit = sys.exit  # mock sys.exit | ||||
|     full_cmd = u' '.join(quote(x) for x in cmd) | ||||
|     _LOGGER.info(u"Running:  %s", full_cmd) | ||||
|     try: | ||||
|         import platformio.__main__ | ||||
|         sys.argv = list(cmd) | ||||
|         sys.exit = mock_exit | ||||
|         return platformio.__main__.main() | ||||
|     except KeyboardInterrupt: | ||||
|         return 1 | ||||
|     except SystemExit as e: | ||||
|         return e.args[0] | ||||
|     except Exception as e: | ||||
|         _LOGGER.error(u"Running platformio failed: %s", e) | ||||
|         _LOGGER.error(u"Please try running %s locally.", full_cmd) | ||||
|     finally: | ||||
|         sys.argv = orig_argv | ||||
|         sys.exit = orig_exit | ||||
|  | ||||
|  | ||||
| def run_miniterm(config, port): | ||||
|     from serial.tools import miniterm | ||||
|     baud_rate = config.get(CONF_LOGGER, {}).get(CONF_BAUD_RATE, 115200) | ||||
|     sys.argv = ['miniterm', '--raw', '--exit-char', '3'] | ||||
|     miniterm.main( | ||||
|         default_port=port, | ||||
|         default_baudrate=baud_rate) | ||||
|  | ||||
|  | ||||
| def write_cpp(config): | ||||
|     _LOGGER.info("Generating C++ source...") | ||||
|     for domain in PRE_INITIALIZE: | ||||
|         if domain in config: | ||||
|             add_component_task(domain, config[domain]) | ||||
|  | ||||
|     # Clear queue | ||||
|     get_variable(None) | ||||
|     add(RawStatement('')) | ||||
|  | ||||
|     for domain, conf in config.iteritems(): | ||||
|         if domain in PRE_INITIALIZE: | ||||
|             continue | ||||
|         add_component_task(domain, conf) | ||||
|  | ||||
|     # Clear queue | ||||
|     get_variable(None) | ||||
|     add(RawStatement('')) | ||||
|     add(RawStatement('')) | ||||
|  | ||||
|     all_code = [] | ||||
|     for exp in _EXPRESSIONS: | ||||
|         if helpers.SIMPLIFY and isinstance(exp, AssignmentExpression) and exp.obj.usages == 0: | ||||
|             exp = exp.rhs | ||||
|         all_code.append(unicode(statement(exp))) | ||||
|  | ||||
|     platformio_ini_s = writer.get_ini_content(config) | ||||
|     ini_path = os.path.join(get_base_path(config), 'platformio.ini') | ||||
|     writer.write_platformio_ini(platformio_ini_s, ini_path) | ||||
|  | ||||
|     code_s = indent('\n'.join(all_code)) | ||||
|     cpp_path = os.path.join(get_base_path(config), 'src', 'main.cpp') | ||||
|     writer.write_cpp(code_s, cpp_path) | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def compile_program(config): | ||||
|     _LOGGER.info("Compiling app...") | ||||
|     return run_platformio('platformio', 'run', '-d', get_base_path(config)) | ||||
|  | ||||
|  | ||||
| def upload_program(config, args, port): | ||||
|     _LOGGER.info("Uploading binary...") | ||||
|     if args.upload_port is not None: | ||||
|         if args.upload_port == 'HELLO': | ||||
|             return run_platformio('platformio', 'run', '-d', get_base_path(config), | ||||
|                                   '-t', 'upload') | ||||
|         else: | ||||
|             return run_platformio('platformio', 'run', '-d', get_base_path(config), | ||||
|                                   '-t', 'upload', '--upload-port', args.upload_port) | ||||
|  | ||||
|     if port is not None: | ||||
|         _LOGGER.info("Serial device discovered, using it for upload") | ||||
|         return run_platformio('platformio', 'run', '-d', get_base_path(config), | ||||
|                               '-t', 'upload', '--upload-port', port) | ||||
|  | ||||
|     if CONF_MANUAL_IP in config[CONF_WIFI]: | ||||
|         host = str(config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP]) | ||||
|     elif CONF_HOSTNAME in config[CONF_WIFI]: | ||||
|         host = config[CONF_WIFI][CONF_HOSTNAME] + u'.local' | ||||
|     else: | ||||
|         host = config[CONF_ESPHOMEYAML][CONF_NAME] + u'.local' | ||||
|  | ||||
|     from esphomeyaml.components import ota | ||||
|     from esphomeyaml import espota | ||||
|  | ||||
|     bin_file = os.path.join(get_base_path(config), '.pioenvs', get_name(config), 'firmware.bin') | ||||
|     if args.host_port is not None: | ||||
|         host_port = args.host_port | ||||
|     else: | ||||
|         host_port = int(os.getenv('ESPHOMEYAML_OTA_HOST_PORT', random.randint(10000, 60000))) | ||||
|     espota_args = ['espota.py', '--debug', '--progress', '-i', host, | ||||
|                    '-p', str(ota.get_port(config)), '-f', bin_file, | ||||
|                    '-a', ota.get_auth(config), '-P', str(host_port)] | ||||
|     return espota.main(espota_args) | ||||
|  | ||||
|  | ||||
| def show_logs(config, args, port): | ||||
|     if port is not None: | ||||
|         run_miniterm(config, port) | ||||
|         return 0 | ||||
|     return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) | ||||
|  | ||||
|  | ||||
| def clean_mqtt(config, args): | ||||
|     return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) | ||||
|  | ||||
|  | ||||
| def setup_log(): | ||||
|     logging.basicConfig(level=logging.INFO) | ||||
|     fmt = "%(levelname)s [%(name)s] %(message)s" | ||||
|     colorfmt = "%(log_color)s{}%(reset)s".format(fmt) | ||||
|     datefmt = '%H:%M:%S' | ||||
|  | ||||
|     logging.getLogger('urllib3').setLevel(logging.WARNING) | ||||
|  | ||||
|     try: | ||||
|         from colorlog import ColoredFormatter | ||||
|         logging.getLogger().handlers[0].setFormatter(ColoredFormatter( | ||||
|             colorfmt, | ||||
|             datefmt=datefmt, | ||||
|             reset=True, | ||||
|             log_colors={ | ||||
|                 'DEBUG': 'cyan', | ||||
|                 'INFO': 'green', | ||||
|                 'WARNING': 'yellow', | ||||
|                 'ERROR': 'red', | ||||
|                 'CRITICAL': 'red', | ||||
|             } | ||||
|         )) | ||||
|     except ImportError: | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     global CONFIG_PATH | ||||
|  | ||||
|     setup_log() | ||||
|  | ||||
|     parser = argparse.ArgumentParser(prog='esphomeyaml') | ||||
|     parser.add_argument('configuration', help='Your YAML configuration file.') | ||||
|     subparsers = parser.add_subparsers(help='Commands', dest='command') | ||||
|     subparsers.required = True | ||||
|     parser_config = subparsers.add_parser('config', | ||||
|                                           help='Validate the configuration and spit it out.') | ||||
|  | ||||
|     parser_compile = subparsers.add_parser('compile', | ||||
|                                            help='Read the configuration and compile a program.') | ||||
|  | ||||
|     parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' | ||||
|                                                          'and upload the latest binary.') | ||||
|     parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " | ||||
|                                                      "For example /dev/cu.SLAB_USBtoUAR.", | ||||
|                                nargs='?', const='HELLO') | ||||
|     parser_upload.add_argument('--host-port', help="Specify the host port.", type=int) | ||||
|  | ||||
|     parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' | ||||
|                                                      'and show all MQTT logs.') | ||||
|     parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.') | ||||
|     parser_logs.add_argument('--username', help='Manually set the username.') | ||||
|     parser_logs.add_argument('--password', help='Manually set the password.') | ||||
|     parser_logs.add_argument('--client-id', help='Manually set the client id.') | ||||
|  | ||||
|     parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' | ||||
|                                                    'upload it, and start MQTT logs.') | ||||
|     parser_run.add_argument('--upload-port', help="Manually specify the upload port to use. " | ||||
|                                                   "For example /dev/cu.SLAB_USBtoUAR.", | ||||
|                             nargs='?', const='HELLO') | ||||
|     parser_run.add_argument('--host-port', help="Specify the host port to use for OTA", type=int) | ||||
|     parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.', | ||||
|                             action='store_true') | ||||
|     parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.') | ||||
|     parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') | ||||
|     parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') | ||||
|     parser_run.add_argument('--client-id', help='Manually set the client id for logs.') | ||||
|  | ||||
|     parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " | ||||
|                                                             "retain messages.") | ||||
|     parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.') | ||||
|     parser_clean.add_argument('--username', help='Manually set the username.') | ||||
|     parser_clean.add_argument('--password', help='Manually set the password.') | ||||
|     parser_clean.add_argument('--client-id', help='Manually set the client id.') | ||||
|  | ||||
|     parser_wizard = subparsers.add_parser('wizard', help="A helpful setup wizard that will guide " | ||||
|                                                          "you through setting up esphomeyaml.") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     if args.command == 'wizard': | ||||
|         return wizard.wizard(args.configuration) | ||||
|  | ||||
|     CONFIG_PATH = args.configuration | ||||
|     config = read_config(CONFIG_PATH) | ||||
|     if config is None: | ||||
|         return 1 | ||||
|  | ||||
|     if args.command == 'config': | ||||
|         print(yaml_util.dump(config)) | ||||
|     elif args.command == 'compile': | ||||
|         exit_code = write_cpp(config) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         exit_code = compile_program(config) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         _LOGGER.info(u"Successfully compiled program.") | ||||
|         return 0 | ||||
|     elif args.command == 'upload': | ||||
|         port = discover_serial_ports() | ||||
|         exit_code = upload_program(config, args, port) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         _LOGGER.info(u"Successfully uploaded program.") | ||||
|         return 0 | ||||
|     elif args.command == 'logs': | ||||
|         port = discover_serial_ports() | ||||
|         return show_logs(config, args, port) | ||||
|     elif args.command == 'clean-mqtt': | ||||
|         return clean_mqtt(config, args) | ||||
|     elif args.command == 'run': | ||||
|         exit_code = write_cpp(config) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         exit_code = compile_program(config) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         _LOGGER.info(u"Successfully compiled program.") | ||||
|         if args.no_logs: | ||||
|             return | ||||
|         port = discover_serial_ports() | ||||
|         exit_code = upload_program(config, args, port) | ||||
|         if exit_code != 0: | ||||
|             return exit_code | ||||
|         _LOGGER.info(u"Successfully uploaded program.") | ||||
|         return show_logs(config, args, port) | ||||
|     else: | ||||
|         print(u"Unknown command {}".format(args.command)) | ||||
|         return 1 | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     sys.exit(main()) | ||||
							
								
								
									
										0
									
								
								esphomeyaml/components/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphomeyaml/components/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										37
									
								
								esphomeyaml/components/ads1115.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphomeyaml/components/ads1115.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_ADDRESS, CONF_ID, CONF_RATE | ||||
| from esphomeyaml.helpers import App, Pvariable, RawExpression, add, HexIntLiteral | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| ADS1115_COMPONENT_CLASS = 'sensor::ADS1115Component' | ||||
|  | ||||
| RATES = { | ||||
|     8: 'ADS1115_RATE_8', | ||||
|     16: 'ADS1115_RATE_16', | ||||
|     32: 'ADS1115_RATE_32', | ||||
|     64: 'ADS1115_RATE_64', | ||||
|     128: 'ADS1115_RATE_128', | ||||
|     250: 'ADS1115_RATE_250', | ||||
|     475: 'ADS1115_RATE_475', | ||||
|     860: 'ADS1115_RATE_860', | ||||
| } | ||||
|  | ||||
| ADS1115_SCHEMA = vol.Schema({ | ||||
|     cv.GenerateID('ads1115'): cv.register_variable_id, | ||||
|     vol.Required(CONF_ADDRESS): cv.i2c_address, | ||||
|     vol.Optional(CONF_RATE): vol.All(vol.Coerce(int), vol.Any(*list(RATES.keys()))), | ||||
| }) | ||||
|  | ||||
| CONFIG_SCHEMA = vol.All(cv.ensure_list, [ADS1115_SCHEMA]) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for conf in config: | ||||
|         address = HexIntLiteral(conf[CONF_ADDRESS]) | ||||
|         rhs = App.make_ads1115_component(address) | ||||
|         ads1115 = Pvariable(ADS1115_COMPONENT_CLASS, conf[CONF_ID], rhs) | ||||
|         if CONF_RATE in conf: | ||||
|             add(ads1115.set_rate(RawExpression(RATES[conf[CONF_RATE]]))) | ||||
							
								
								
									
										29
									
								
								esphomeyaml/components/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphomeyaml/components/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_DEVICE_CLASS, CONF_INVERTED | ||||
| from esphomeyaml.helpers import add, setup_mqtt_component | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_INVERTED): cv.boolean, | ||||
| }) | ||||
|  | ||||
| DEVICE_CLASSES = [ | ||||
|     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', | ||||
|     'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', | ||||
|     'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke', | ||||
|     'sound', 'vibration', 'window' | ||||
| ] | ||||
|  | ||||
| DEVICE_CLASSES_MSG = "Unknown device class. Must be one of {}".format(', '.join(DEVICE_CLASSES)) | ||||
|  | ||||
| MQTT_BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_DEVICE_CLASS): vol.All(vol.Lower, | ||||
|                                              vol.Any(*DEVICE_CLASSES, msg=DEVICE_CLASSES_MSG)), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def setup_mqtt_binary_sensor(obj, config, skip_device_class=False): | ||||
|     if not skip_device_class and CONF_DEVICE_CLASS in config: | ||||
|         add(obj.set_device_class(config[CONF_DEVICE_CLASS])) | ||||
|     setup_mqtt_component(obj, config) | ||||
							
								
								
									
										21
									
								
								esphomeyaml/components/binary_sensor/gpio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphomeyaml/components/binary_sensor/gpio.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import binary_sensor | ||||
| from esphomeyaml.const import CONF_DEVICE_CLASS, CONF_ID, CONF_INVERTED, CONF_NAME, CONF_PIN | ||||
| from esphomeyaml.helpers import App, add, exp_gpio_input_pin, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('gpio_binary_sensor'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.GPIO_INPUT_PIN_SCHEMA | ||||
| }).extend(binary_sensor.MQTT_BINARY_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_gpio_binary_sensor(exp_gpio_input_pin(config[CONF_PIN]), | ||||
|                                       config[CONF_NAME], config.get(CONF_DEVICE_CLASS)) | ||||
|     gpio = variable('Application::SimpleBinarySensor', config[CONF_ID], rhs) | ||||
|     if CONF_INVERTED in config: | ||||
|         add(gpio.Pgpio.set_inverted(config[CONF_INVERTED])) | ||||
|     binary_sensor.setup_mqtt_binary_sensor(gpio.Pmqtt, config, skip_device_class=True) | ||||
							
								
								
									
										14
									
								
								esphomeyaml/components/binary_sensor/status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphomeyaml/components/binary_sensor/status.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import binary_sensor | ||||
| from esphomeyaml.const import CONF_ID, CONF_NAME | ||||
| from esphomeyaml.helpers import App, Pvariable | ||||
|  | ||||
| PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('status_binary_sensor'): cv.register_variable_id, | ||||
| }).extend(binary_sensor.MQTT_BINARY_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_status_binary_sensor(config[CONF_NAME]) | ||||
|     gpio = Pvariable('binary_sensor::MQTTBinarySensorComponent', config[CONF_ID], rhs) | ||||
|     binary_sensor.setup_mqtt_binary_sensor(gpio.Pmqtt, config) | ||||
							
								
								
									
										20
									
								
								esphomeyaml/components/dallas.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphomeyaml/components/dallas.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.const import CONF_ID, CONF_PIN, CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, Pvariable | ||||
|  | ||||
| DALLAS_COMPONENT_CLASS = 'sensor::DallasComponent' | ||||
|  | ||||
| CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ | ||||
|     cv.GenerateID('dallas'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.input_output_pin, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| })]) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for conf in config: | ||||
|         rhs = App.make_dallas_component(conf[CONF_PIN], conf.get(CONF_UPDATE_INTERVAL)) | ||||
|         Pvariable(DALLAS_COMPONENT_CLASS, conf[CONF_ID], rhs) | ||||
							
								
								
									
										23
									
								
								esphomeyaml/components/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphomeyaml/components/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, \ | ||||
|     CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC | ||||
| from esphomeyaml.helpers import add, setup_mqtt_component | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.publish_topic, | ||||
|     vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.subscribe_topic, | ||||
| }).extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def setup_mqtt_fan(obj, config): | ||||
|     if CONF_OSCILLATION_STATE_TOPIC in config: | ||||
|         add(obj.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC])) | ||||
|     if CONF_OSCILLATION_COMMAND_TOPIC in config: | ||||
|         add(obj.set_custom_oscillation_command_topic(config[CONF_OSCILLATION_COMMAND_TOPIC])) | ||||
|     if CONF_SPEED_STATE_TOPIC in config: | ||||
|         add(obj.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC])) | ||||
|     if CONF_SPEED_COMMAND_TOPIC in config: | ||||
|         add(obj.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC])) | ||||
|     setup_mqtt_component(obj, config) | ||||
							
								
								
									
										23
									
								
								esphomeyaml/components/fan/binary.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphomeyaml/components/fan/binary.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import fan | ||||
| from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT | ||||
| from esphomeyaml.helpers import App, add, get_variable, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = fan.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('binary_fan'): cv.register_variable_id, | ||||
|     vol.Required(CONF_OUTPUT): cv.variable_id, | ||||
|     vol.Optional(CONF_OSCILLATION_OUTPUT): cv.variable_id, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     output = get_variable(config[CONF_OUTPUT]) | ||||
|     rhs = App.make_fan(config[CONF_NAME]) | ||||
|     fan_struct = variable('Application::FanStruct', config[CONF_ID], rhs) | ||||
|     add(fan_struct.Poutput.set_binary(output)) | ||||
|     if CONF_OSCILLATION_OUTPUT in config: | ||||
|         oscillation_output = get_variable(config[CONF_OSCILLATION_OUTPUT]) | ||||
|         add(fan_struct.Poutput.set_oscillation(oscillation_output)) | ||||
|     fan.setup_mqtt_fan(fan_struct.Pmqtt, config) | ||||
							
								
								
									
										40
									
								
								esphomeyaml/components/fan/speed.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphomeyaml/components/fan/speed.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import fan | ||||
| from esphomeyaml.const import CONF_HIGH, CONF_ID, CONF_LOW, \ | ||||
|     CONF_MEDIUM, CONF_NAME, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_SPEED, \ | ||||
|     CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC | ||||
| from esphomeyaml.helpers import App, add, get_variable, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = fan.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('speed_fan'): cv.register_variable_id, | ||||
|     vol.Required(CONF_OUTPUT): cv.variable_id, | ||||
|     vol.Optional(CONF_SPEED_STATE_TOPIC): cv.publish_topic, | ||||
|     vol.Optional(CONF_SPEED_COMMAND_TOPIC): cv.subscribe_topic, | ||||
|     vol.Optional(CONF_OSCILLATION_OUTPUT): cv.variable_id, | ||||
|     vol.Optional(CONF_SPEED): vol.Schema({ | ||||
|         vol.Required(CONF_LOW): cv.zero_to_one_float, | ||||
|         vol.Required(CONF_MEDIUM): cv.zero_to_one_float, | ||||
|         vol.Required(CONF_HIGH): cv.zero_to_one_float, | ||||
|     }), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     output = get_variable(config[CONF_OUTPUT]) | ||||
|     rhs = App.make_fan(config[CONF_NAME]) | ||||
|     fan_struct = variable('Application::FanStruct', config[CONF_ID], rhs) | ||||
|     if CONF_SPEED in config: | ||||
|         speeds = config[CONF_SPEED] | ||||
|         add(fan_struct.Poutput.set_speed(output, 0.0, | ||||
|                                          speeds[CONF_LOW], | ||||
|                                          speeds[CONF_MEDIUM], | ||||
|                                          speeds[CONF_HIGH])) | ||||
|     else: | ||||
|         add(fan_struct.Poutput.set_speed(output)) | ||||
|  | ||||
|     if CONF_OSCILLATION_OUTPUT in config: | ||||
|         oscillation_output = get_variable(config[CONF_OSCILLATION_OUTPUT]) | ||||
|         add(fan_struct.Poutput.set_oscillation(oscillation_output)) | ||||
|     fan.setup_mqtt_fan(fan_struct.Pmqtt, config) | ||||
							
								
								
									
										16
									
								
								esphomeyaml/components/i2c.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphomeyaml/components/i2c.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.const import CONF_FREQUENCY, CONF_SCL, CONF_SDA | ||||
| from esphomeyaml.helpers import App, add | ||||
|  | ||||
| CONFIG_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_SDA, default='SDA'): pins.input_output_pin, | ||||
|     vol.Required(CONF_SCL, default='SCL'): pins.input_output_pin, | ||||
|     vol.Optional(CONF_FREQUENCY): vol.All(cv.only_on_esp32, cv.positive_int), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     add(App.init_i2c(config[CONF_SDA], config[CONF_SCL], config.get(CONF_FREQUENCY))) | ||||
							
								
								
									
										23
									
								
								esphomeyaml/components/ir_transmitter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphomeyaml/components/ir_transmitter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 | ||||
| from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32] | ||||
|  | ||||
| IR_TRANSMITTER_COMPONENT_CLASS = 'switch_::IRTransmitterComponent' | ||||
|  | ||||
| CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ | ||||
|     cv.GenerateID('ir_transmitter'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
|     vol.Optional(CONF_CARRIER_DUTY_PERCENT): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), | ||||
| })]) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for conf in config: | ||||
|         pin = exp_gpio_output_pin(conf[CONF_PIN]) | ||||
|         rhs = App.make_ir_transmitter(pin, conf.get(CONF_CARRIER_DUTY_PERCENT)) | ||||
|         Pvariable(IR_TRANSMITTER_COMPONENT_CLASS, conf[CONF_ID], rhs) | ||||
							
								
								
									
										13
									
								
								esphomeyaml/components/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								esphomeyaml/components/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_DEFAULT_TRANSITION_LENGTH | ||||
| from esphomeyaml.helpers import add, setup_mqtt_component | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|  | ||||
| }).extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def setup_mqtt_light_component(obj, config): | ||||
|     if CONF_DEFAULT_TRANSITION_LENGTH in config: | ||||
|         add(obj.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH])) | ||||
|     setup_mqtt_component(obj, config) | ||||
							
								
								
									
										19
									
								
								esphomeyaml/components/light/binary.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphomeyaml/components/light/binary.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import light | ||||
| from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_OUTPUT | ||||
| from esphomeyaml.helpers import App, get_variable, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('binary_light'): cv.register_variable_id, | ||||
|     vol.Required(CONF_OUTPUT): cv.variable_id, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     output = get_variable(config[CONF_OUTPUT]) | ||||
|     rhs = App.make_binary_light(config[CONF_NAME], output) | ||||
|     light_struct = variable('Application::LightStruct', config[CONF_ID], rhs) | ||||
|     light.setup_mqtt_light_component(light_struct.Pmqtt, config) | ||||
							
								
								
									
										23
									
								
								esphomeyaml/components/light/monochromatic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphomeyaml/components/light/monochromatic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import light | ||||
| from esphomeyaml.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, \ | ||||
|     CONF_NAME, CONF_OUTPUT | ||||
| from esphomeyaml.helpers import App, add, get_variable, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('monochromatic_light'): cv.register_variable_id, | ||||
|     vol.Required(CONF_OUTPUT): cv.variable_id, | ||||
|     vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, | ||||
|     vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     output = get_variable(config[CONF_OUTPUT]) | ||||
|     rhs = App.make_monochromatic_light(config[CONF_NAME], output) | ||||
|     light_struct = variable('Application::LightStruct', config[CONF_ID], rhs) | ||||
|     if CONF_GAMMA_CORRECT in config: | ||||
|         add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) | ||||
|     light.setup_mqtt_light_component(light_struct.Pmqtt, config) | ||||
							
								
								
									
										27
									
								
								esphomeyaml/components/light/rgb.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphomeyaml/components/light/rgb.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import light | ||||
| from esphomeyaml.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, \ | ||||
|     CONF_GREEN, CONF_ID, CONF_NAME, CONF_RED | ||||
| from esphomeyaml.helpers import App, add, get_variable, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('rgb_light'): cv.register_variable_id, | ||||
|     vol.Required(CONF_RED): cv.variable_id, | ||||
|     vol.Required(CONF_GREEN): cv.variable_id, | ||||
|     vol.Required(CONF_BLUE): cv.variable_id, | ||||
|     vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, | ||||
|     vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     red = get_variable(config[CONF_RED]) | ||||
|     green = get_variable(config[CONF_GREEN]) | ||||
|     blue = get_variable(config[CONF_BLUE]) | ||||
|     rhs = App.make_rgb_light(config[CONF_NAME], red, green, blue) | ||||
|     light_struct = variable('Application::LightStruct', config[CONF_ID], rhs) | ||||
|     if CONF_GAMMA_CORRECT in config: | ||||
|         add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) | ||||
|     light.setup_mqtt_light_component(light_struct.Pmqtt, config) | ||||
							
								
								
									
										29
									
								
								esphomeyaml/components/light/rgbw.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphomeyaml/components/light/rgbw.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import light | ||||
| from esphomeyaml.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, \ | ||||
|     CONF_GREEN, CONF_ID, CONF_NAME, CONF_RED, CONF_WHITE | ||||
| from esphomeyaml.helpers import App, get_variable, variable, add | ||||
|  | ||||
| PLATFORM_SCHEMA = light.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('rgbw_light'): cv.register_variable_id, | ||||
|     vol.Required(CONF_RED): cv.variable_id, | ||||
|     vol.Required(CONF_GREEN): cv.variable_id, | ||||
|     vol.Required(CONF_BLUE): cv.variable_id, | ||||
|     vol.Required(CONF_WHITE): cv.variable_id, | ||||
|     vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float, | ||||
|     vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     red = get_variable(config[CONF_RED]) | ||||
|     green = get_variable(config[CONF_GREEN]) | ||||
|     blue = get_variable(config[CONF_BLUE]) | ||||
|     white = get_variable(config[CONF_WHITE]) | ||||
|     rhs = App.make_rgbw_light(config[CONF_NAME], red, green, blue, white) | ||||
|     light_struct = variable('Application::LightStruct', config[CONF_ID], rhs) | ||||
|     if CONF_GAMMA_CORRECT in config: | ||||
|         add(light_struct.Poutput.set_gamma_correct(config[CONF_GAMMA_CORRECT])) | ||||
|     light.setup_mqtt_light_component(light_struct.Pmqtt, config) | ||||
							
								
								
									
										60
									
								
								esphomeyaml/components/logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								esphomeyaml/components/logger.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_BAUD_RATE, CONF_ID, CONF_LEVEL, CONF_LOGGER, CONF_LOGS, \ | ||||
|     CONF_LOG_TOPIC, CONF_TX_BUFFER_SIZE | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
| from esphomeyaml.helpers import App, Pvariable, RawExpression, add, exp_empty_optional | ||||
|  | ||||
| LOG_LEVELS = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE'] | ||||
|  | ||||
| is_log_level = vol.All(vol.Upper, vol.Any(*LOG_LEVELS)) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.ID_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_LOGGER): cv.register_variable_id, | ||||
|     vol.Optional(CONF_BAUD_RATE): cv.positive_int, | ||||
|     vol.Optional(CONF_LOG_TOPIC): vol.Any(None, '', cv.publish_topic), | ||||
|     vol.Optional(CONF_TX_BUFFER_SIZE): cv.positive_int, | ||||
|     vol.Optional(CONF_LEVEL): is_log_level, | ||||
|     vol.Optional(CONF_LOGS): vol.Schema({ | ||||
|         cv.string: is_log_level, | ||||
|     }) | ||||
| }) | ||||
|  | ||||
|  | ||||
| def esphomelib_log_level(level): | ||||
|     return u'ESPHOMELIB_LOG_LEVEL_{}'.format(level) | ||||
|  | ||||
|  | ||||
| def exp_log_level(level): | ||||
|     return RawExpression(esphomelib_log_level(level)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     baud_rate = config.get(CONF_BAUD_RATE) | ||||
|     if baud_rate is None and CONF_LOG_TOPIC in config: | ||||
|         baud_rate = 115200 | ||||
|     log_topic = None | ||||
|     if CONF_LOG_TOPIC in config: | ||||
|         if not config[CONF_LOG_TOPIC]: | ||||
|             log_topic = exp_empty_optional(u'std::string') | ||||
|         else: | ||||
|             log_topic = config[CONF_LOG_TOPIC] | ||||
|     rhs = App.init_log(baud_rate, log_topic) | ||||
|     log = Pvariable(u'LogComponent', config[CONF_ID], rhs) | ||||
|     if CONF_TX_BUFFER_SIZE in config: | ||||
|         add(log.set_tx_buffer_size(config[CONF_TX_BUFFER_SIZE])) | ||||
|     if CONF_LEVEL in config: | ||||
|         add(log.set_global_log_level(exp_log_level(config[CONF_LEVEL]))) | ||||
|     for tag, level in config.get(CONF_LOGS, {}).iteritems(): | ||||
|         global_level = config.get(CONF_LEVEL, 'DEBUG') | ||||
|         if LOG_LEVELS.index(level) > LOG_LEVELS.index(global_level): | ||||
|             raise ESPHomeYAMLError(u"The local log level {} for {} must be less severe than the " | ||||
|                                    u"global log level {}.".format(level, tag, global_level)) | ||||
|         add(log.set_log_level(tag, exp_log_level(level))) | ||||
|  | ||||
|  | ||||
| def get_build_flags(config): | ||||
|     if CONF_LEVEL in config: | ||||
|         return u'-DESPHOMELIB_LOG_LEVEL={}'.format(esphomelib_log_level(config[CONF_LEVEL])) | ||||
|     return u'' | ||||
							
								
								
									
										74
									
								
								esphomeyaml/components/mqtt.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphomeyaml/components/mqtt.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_DISCOVERY, \ | ||||
|     CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, CONF_ID, CONF_MQTT, CONF_PASSWORD, \ | ||||
|     CONF_PAYLOAD, CONF_PORT, CONF_QOS, CONF_RETAIN, CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME, \ | ||||
|     CONF_WILL_MESSAGE, CONF_CLIENT_ID | ||||
| from esphomeyaml.helpers import App, Pvariable, StructInitializer, add, exp_empty_optional | ||||
|  | ||||
| MQTT_WILL_BIRTH_SCHEMA = vol.Any(None, vol.Schema({ | ||||
|     vol.Required(CONF_TOPIC): cv.publish_topic, | ||||
|     vol.Required(CONF_PAYLOAD): cv.mqtt_payload, | ||||
|     vol.Optional(CONF_QOS, default=0): vol.All(vol.Coerce(int), vol.In([0, 1, 2])), | ||||
|     vol.Optional(CONF_RETAIN, default=True): cv.boolean, | ||||
| })) | ||||
|  | ||||
|  | ||||
| def validate_broker(value): | ||||
|     value = cv.string_strict(value) | ||||
|     if value.endswith(u'.local'): | ||||
|         raise vol.Invalid(u"MQTT server addresses ending with '.local' are currently unsupported." | ||||
|                           u" Please specify the static IP instead.") | ||||
|     if u':' in value: | ||||
|         raise vol.Invalid(u"Please specify the port using the port: option") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.ID_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_MQTT): cv.register_variable_id, | ||||
|     vol.Required(CONF_BROKER): validate_broker, | ||||
|     vol.Optional(CONF_PORT, default=1883): cv.port, | ||||
|     vol.Optional(CONF_USERNAME, default=''): cv.string, | ||||
|     vol.Optional(CONF_PASSWORD, default=''): cv.string, | ||||
|     vol.Optional(CONF_CLIENT_ID): vol.All(cv.string, vol.Length(max=23)), | ||||
|     vol.Optional(CONF_DISCOVERY): cv.boolean, | ||||
|     vol.Optional(CONF_DISCOVERY_RETAIN): cv.boolean, | ||||
|     vol.Optional(CONF_DISCOVERY_PREFIX): cv.publish_topic, | ||||
|     vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, | ||||
|     vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, | ||||
|     vol.Optional(CONF_TOPIC_PREFIX): cv.publish_topic, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def exp_mqtt_message(config): | ||||
|     if config is None: | ||||
|         return exp_empty_optional('mqtt::MQTTMessage') | ||||
|     exp = StructInitializer( | ||||
|         'mqtt::MQTTMessage', | ||||
|         ('topic', config[CONF_TOPIC]), | ||||
|         ('payload', config[CONF_PAYLOAD]), | ||||
|         ('qos', config[CONF_QOS]), | ||||
|         ('retain', config[CONF_RETAIN]) | ||||
|     ) | ||||
|     return exp | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.init_mqtt(config[CONF_BROKER], config[CONF_PORT], | ||||
|                         config[CONF_USERNAME], config[CONF_PASSWORD]) | ||||
|     mqtt = Pvariable('mqtt::MQTTClientComponent', config[CONF_ID], rhs) | ||||
|     if not config.get(CONF_DISCOVERY, True): | ||||
|         add(mqtt.disable_discovery()) | ||||
|     if CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: | ||||
|         discovery_retain = config.get(CONF_DISCOVERY_RETAIN, True) | ||||
|         discovery_prefix = config.get(CONF_DISCOVERY_PREFIX, 'homeassistant') | ||||
|         add(mqtt.set_discovery_info(discovery_prefix, discovery_retain)) | ||||
|     if CONF_BIRTH_MESSAGE in config: | ||||
|         add(mqtt.set_birth_message(config[CONF_BIRTH_MESSAGE])) | ||||
|     if CONF_WILL_MESSAGE in config: | ||||
|         add(mqtt.set_last_will(config[CONF_WILL_MESSAGE])) | ||||
|     if CONF_TOPIC_PREFIX in config: | ||||
|         add(mqtt.set_topic_prefix(config[CONF_TOPIC_PREFIX])) | ||||
|     if CONF_CLIENT_ID in config: | ||||
|         add(mqtt.set_client_id(config[CONF_CLIENT_ID])) | ||||
							
								
								
									
										44
									
								
								esphomeyaml/components/ota.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphomeyaml/components/ota.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import hashlib | ||||
| import logging | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_ID, CONF_OTA, CONF_PASSWORD, CONF_PORT, CONF_SAFE_MODE, \ | ||||
|     ESP_PLATFORM_ESP8266, ESP_PLATFORM_ESP32 | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
| from esphomeyaml.helpers import App, Pvariable, add | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.ID_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_OTA): cv.register_variable_id, | ||||
|     vol.Optional(CONF_SAFE_MODE, default=True): cv.boolean, | ||||
|     # TODO Num attempts + wait time | ||||
|     vol.Optional(CONF_PORT): cv.port, | ||||
|     vol.Optional(CONF_PASSWORD): cv.string, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.init_ota() | ||||
|     ota = Pvariable('OTAComponent', config[CONF_ID], rhs) | ||||
|     if CONF_PASSWORD in config: | ||||
|         h = hashlib.md5(config[CONF_PASSWORD].encode()).hexdigest() | ||||
|         add(ota.set_auth_password_hash(h)) | ||||
|     if config[CONF_SAFE_MODE]: | ||||
|         add(ota.start_safe_mode()) | ||||
|  | ||||
|  | ||||
| def get_port(config): | ||||
|     if CONF_PORT in config[CONF_OTA]: | ||||
|         return config[CONF_OTA][CONF_PORT] | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         return 3232 | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         return 8266 | ||||
|     raise ESPHomeYAMLError(u"Invalid ESP Platform for ESP OTA port.") | ||||
|  | ||||
|  | ||||
| def get_auth(config): | ||||
|     return config[CONF_OTA].get(CONF_PASSWORD, '') | ||||
							
								
								
									
										25
									
								
								esphomeyaml/components/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphomeyaml/components/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_POWER_SUPPLY, CONF_INVERTED, CONF_MAX_POWER | ||||
| from esphomeyaml.helpers import get_variable, add | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_POWER_SUPPLY): cv.variable_id, | ||||
|     vol.Optional(CONF_INVERTED): cv.boolean, | ||||
| }).extend(cv.REQUIRED_ID_SCHEMA.schema) | ||||
|  | ||||
| FLOAT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_MAX_POWER): cv.zero_to_one_float, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def setup_output_platform(obj, config, skip_power_supply=False): | ||||
|     if CONF_INVERTED in config: | ||||
|         add(obj.set_inverted(config[CONF_INVERTED])) | ||||
|     if not skip_power_supply and CONF_POWER_SUPPLY in config: | ||||
|         power_supply = get_variable(config[CONF_POWER_SUPPLY]) | ||||
|         add(obj.set_power_supply(power_supply)) | ||||
|     if CONF_MAX_POWER in config: | ||||
|         add(obj.set_max_power(config[CONF_MAX_POWER])) | ||||
							
								
								
									
										24
									
								
								esphomeyaml/components/output/esp8266_pwm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphomeyaml/components/output/esp8266_pwm.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import output | ||||
| from esphomeyaml.const import CONF_ID, CONF_PIN, \ | ||||
|     ESP_PLATFORM_ESP8266 | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
| from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin, get_gpio_pin_number | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] | ||||
|  | ||||
| PLATFORM_SCHEMA = output.FLOAT_PLATFORM_SCHEMA.extend({ | ||||
|     vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     if get_gpio_pin_number(config[CONF_PIN]) >= 16: | ||||
|         # Too difficult to do in config validation | ||||
|         raise ESPHomeYAMLError(u"ESP8266: Only pins 0-16 support PWM.") | ||||
|     pin = exp_gpio_output_pin(config[CONF_PIN]) | ||||
|     rhs = App.make_esp8266_pwm_output(pin) | ||||
|     gpio = Pvariable('output::ESP8266PWMOutput', config[CONF_ID], rhs) | ||||
|     output.setup_output_platform(gpio, config) | ||||
							
								
								
									
										17
									
								
								esphomeyaml/components/output/gpio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								esphomeyaml/components/output/gpio.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import output | ||||
| from esphomeyaml.const import CONF_ID, CONF_PIN | ||||
| from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin | ||||
|  | ||||
| PLATFORM_SCHEMA = output.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     pin = exp_gpio_output_pin(config[CONF_PIN]) | ||||
|     rhs = App.make_gpio_output(pin) | ||||
|     gpio = Pvariable('output::GPIOBinaryOutputComponent', config[CONF_ID], rhs) | ||||
|     output.setup_output_platform(gpio, config) | ||||
							
								
								
									
										38
									
								
								esphomeyaml/components/output/ledc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphomeyaml/components/output/ledc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import output | ||||
| from esphomeyaml.const import APB_CLOCK_FREQ, CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \ | ||||
|     CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 | ||||
| from esphomeyaml.helpers import App, Pvariable, add | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32] | ||||
|  | ||||
|  | ||||
| def validate_frequency_bit_depth(obj): | ||||
|     frequency = obj.get(CONF_FREQUENCY, 1000) | ||||
|     bit_depth = obj.get(CONF_BIT_DEPTH, 12) | ||||
|     max_freq = APB_CLOCK_FREQ / (2**bit_depth) | ||||
|     if frequency > max_freq: | ||||
|         raise vol.Invalid('Maximum frequency for bit depth {} is {}'.format(bit_depth, max_freq)) | ||||
|     return obj | ||||
|  | ||||
|  | ||||
| PLATFORM_SCHEMA = vol.All(output.FLOAT_PLATFORM_SCHEMA.extend({ | ||||
|     vol.Required(CONF_PIN): vol.All(pins.output_pin, vol.Range(min=0, max=33)), | ||||
|     vol.Optional(CONF_FREQUENCY): cv.frequency, | ||||
|     vol.Optional(CONF_BIT_DEPTH): vol.All(vol.Coerce(int), vol.Range(min=1, max=15)), | ||||
|     vol.Optional(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=0, max=15)) | ||||
| }), validate_frequency_bit_depth) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     frequency = config.get(CONF_FREQUENCY) | ||||
|     if frequency is None and CONF_BIT_DEPTH in config: | ||||
|         frequency = 1000 | ||||
|     rhs = App.make_ledc_output(config[CONF_PIN], frequency, config.get(CONF_BIT_DEPTH)) | ||||
|     ledc = Pvariable('output::LEDCOutputComponent', config[CONF_ID], rhs) | ||||
|     if CONF_CHANNEL in config: | ||||
|         add(ledc.set_channel(config[CONF_CHANNEL])) | ||||
|     output.setup_output_platform(ledc, config) | ||||
							
								
								
									
										25
									
								
								esphomeyaml/components/output/pca9685.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphomeyaml/components/output/pca9685.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import output | ||||
| from esphomeyaml.components.pca9685 import PCA9685_COMPONENT_TYPE | ||||
| from esphomeyaml.const import CONF_CHANNEL, CONF_ID, CONF_PCA9685_ID, CONF_POWER_SUPPLY | ||||
| from esphomeyaml.helpers import Pvariable, get_variable | ||||
|  | ||||
| DEPENDENCIES = ['pca9685'] | ||||
|  | ||||
| PLATFORM_SCHEMA = output.FLOAT_PLATFORM_SCHEMA.extend({ | ||||
|     vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), | ||||
|                                         vol.Range(min=0, max=15)), | ||||
|     vol.Optional(CONF_PCA9685_ID): cv.variable_id, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     power_supply = None | ||||
|     if CONF_POWER_SUPPLY in config: | ||||
|         power_supply = get_variable(config[CONF_POWER_SUPPLY]) | ||||
|     pca9685 = get_variable(config.get(CONF_PCA9685_ID), PCA9685_COMPONENT_TYPE) | ||||
|     rhs = pca9685.create_channel(config[CONF_CHANNEL], power_supply) | ||||
|     out = Pvariable('output::PCA9685OutputComponent::Channel', config[CONF_ID], rhs) | ||||
|     output.setup_output_platform(out, config, skip_power_supply=True) | ||||
							
								
								
									
										33
									
								
								esphomeyaml/components/pca9685.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphomeyaml/components/pca9685.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_ADDRESS, CONF_FREQUENCY, CONF_ID, CONF_PHASE_BALANCER | ||||
| from esphomeyaml.helpers import App, HexIntLiteral, Pvariable, RawExpression, add | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| PHASE_BALANCERS = ['None', 'Linear', 'Weaved'] | ||||
|  | ||||
| PCA9685_COMPONENT_TYPE = 'output::PCA9685OutputComponent' | ||||
|  | ||||
| PCA9685_SCHEMA = vol.Schema({ | ||||
|     cv.GenerateID('pca9685'): cv.register_variable_id, | ||||
|     vol.Required(CONF_FREQUENCY): vol.All(cv.frequency, | ||||
|                                           vol.Range(min=24, max=1526)), | ||||
|     vol.Optional(CONF_PHASE_BALANCER): vol.All(vol.Title, vol.Any(*PHASE_BALANCERS)), | ||||
|     vol.Optional(CONF_ADDRESS): cv.i2c_address, | ||||
| }) | ||||
|  | ||||
| CONFIG_SCHEMA = vol.All(cv.ensure_list, [PCA9685_SCHEMA]) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for conf in config: | ||||
|         rhs = App.make_pca9685_component(conf.get(CONF_FREQUENCY)) | ||||
|         pca9685 = Pvariable(PCA9685_COMPONENT_TYPE, conf[CONF_ID], rhs) | ||||
|         if CONF_ADDRESS in conf: | ||||
|             add(pca9685.set_address(HexIntLiteral(conf[CONF_ADDRESS]))) | ||||
|         if CONF_PHASE_BALANCER in conf: | ||||
|             phase_balancer = RawExpression(u'PCA9685_PhaseBalancer_{}'.format( | ||||
|                 conf[CONF_PHASE_BALANCER])) | ||||
|             add(pca9685.set_phase_balancer(phase_balancer)) | ||||
							
								
								
									
										25
									
								
								esphomeyaml/components/power_supply.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphomeyaml/components/power_supply.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN | ||||
| from esphomeyaml.helpers import App, Pvariable, add, exp_gpio_output_pin | ||||
|  | ||||
| POWER_SUPPLY_SCHEMA = cv.REQUIRED_ID_SCHEMA.extend({ | ||||
|     vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
|     vol.Optional(CONF_ENABLE_TIME): cv.positive_time_period, | ||||
|     vol.Optional(CONF_KEEP_ON_TIME): cv.positive_time_period, | ||||
| }) | ||||
|  | ||||
| CONFIG_SCHEMA = vol.All(cv.ensure_list, [POWER_SUPPLY_SCHEMA]) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for conf in config: | ||||
|         pin = exp_gpio_output_pin(conf[CONF_PIN]) | ||||
|         rhs = App.make_power_supply(pin) | ||||
|         psu = Pvariable('PowerSupplyComponent', conf[CONF_ID], rhs) | ||||
|         if CONF_ENABLE_TIME in conf: | ||||
|             add(psu.set_enable_time(conf[CONF_ENABLE_TIME])) | ||||
|         if CONF_KEEP_ON_TIME in conf: | ||||
|             add(psu.set_keep_on_time(conf[CONF_KEEP_ON_TIME])) | ||||
							
								
								
									
										100
									
								
								esphomeyaml/components/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								esphomeyaml/components/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_EXPIRE_AFTER, \ | ||||
|     CONF_EXPONENTIAL_MOVING_AVERAGE, CONF_FILTERS, CONF_FILTER_NAN, CONF_FILTER_OUT, CONF_ICON, \ | ||||
|     CONF_ID, CONF_LAMBDA, CONF_MULTIPLY, CONF_NAME, CONF_OFFSET, CONF_SEND_EVERY, \ | ||||
|     CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE | ||||
| from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add, \ | ||||
|     setup_mqtt_component | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|  | ||||
| }) | ||||
|  | ||||
| FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.Any( | ||||
|     # TODO Fix weird voluptuous error messages | ||||
|     vol.Schema({vol.Required(CONF_OFFSET): vol.Coerce(float)}), | ||||
|     vol.Schema({vol.Required(CONF_MULTIPLY): vol.Coerce(float)}), | ||||
|     vol.Schema({vol.Required(CONF_FILTER_OUT): vol.Coerce(float)}), | ||||
|     vol.Schema({vol.Required(CONF_FILTER_NAN): None}), | ||||
|     vol.Schema({ | ||||
|         vol.Required(CONF_SLIDING_WINDOW_MOVING_AVERAGE): vol.Schema({ | ||||
|             vol.Required(CONF_WINDOW_SIZE): cv.positive_not_null_int, | ||||
|             vol.Required(CONF_SEND_EVERY): cv.positive_not_null_int, | ||||
|         }) | ||||
|     }), | ||||
|     vol.Schema({ | ||||
|         vol.Required(CONF_EXPONENTIAL_MOVING_AVERAGE): vol.Schema({ | ||||
|             vol.Required(CONF_ALPHA): cv.positive_float, | ||||
|             vol.Required(CONF_SEND_EVERY): cv.positive_not_null_int, | ||||
|         }) | ||||
|     }), | ||||
|     vol.Schema({vol.Required(CONF_LAMBDA): cv.string_strict}), | ||||
| )]) | ||||
|  | ||||
| MQTT_SENSOR_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_NAME): cv.string, | ||||
|     vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, | ||||
|     vol.Optional(CONF_ICON): cv.icon, | ||||
|     vol.Optional(CONF_ACCURACY_DECIMALS): vol.Coerce(int), | ||||
|     vol.Optional(CONF_EXPIRE_AFTER): vol.Any(None, cv.positive_time_period), | ||||
|     vol.Optional(CONF_FILTERS): FILTERS_SCHEMA | ||||
| }) | ||||
|  | ||||
| MQTT_SENSOR_ID_SCHEMA = MQTT_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID('mqtt_sensor'): cv.register_variable_id, | ||||
| }) | ||||
|  | ||||
| OffsetFilter = MockObj('new sensor::OffsetFilter') | ||||
| MultiplyFilter = MockObj('new sensor::MultiplyFilter') | ||||
| FilterOutValueFilter = MockObj('new sensor::FilterOutValueFilter') | ||||
| FilterOutNANFilter = MockObj('new sensor::FilterOutNANFilter') | ||||
| SlidingWindowMovingAverageFilter = MockObj('new sensor::SlidingWindowMovingAverageFilter') | ||||
| ExponentialMovingAverageFilter = MockObj('new sensor::ExponentialMovingAverageFilter') | ||||
| LambdaFilter = MockObj('new sensor::LambdaFilter') | ||||
|  | ||||
|  | ||||
| def setup_filter(config): | ||||
|     if CONF_OFFSET in config: | ||||
|         return OffsetFilter(config[CONF_OFFSET]) | ||||
|     if CONF_MULTIPLY in config: | ||||
|         return MultiplyFilter(config[CONF_MULTIPLY]) | ||||
|     if CONF_FILTER_OUT in config: | ||||
|         return FilterOutValueFilter(config[CONF_FILTER_OUT]) | ||||
|     if CONF_FILTER_NAN in config: | ||||
|         return FilterOutNANFilter() | ||||
|     if CONF_SLIDING_WINDOW_MOVING_AVERAGE in config: | ||||
|         conf = config[CONF_SLIDING_WINDOW_MOVING_AVERAGE] | ||||
|         return SlidingWindowMovingAverageFilter(conf[CONF_WINDOW_SIZE], conf[CONF_SEND_EVERY]) | ||||
|     if CONF_EXPONENTIAL_MOVING_AVERAGE in config: | ||||
|         conf = config[CONF_EXPONENTIAL_MOVING_AVERAGE] | ||||
|         return ExponentialMovingAverageFilter(conf[CONF_ALPHA], conf[CONF_SEND_EVERY]) | ||||
|     if CONF_LAMBDA in config: | ||||
|         s = '[](float x) -> Optional<float> {{ return {}; }}'.format(config[CONF_LAMBDA]) | ||||
|         return LambdaFilter(RawExpression(s)) | ||||
|     raise ValueError("Filter unsupported: {}".format(config)) | ||||
|  | ||||
|  | ||||
| def setup_mqtt_sensor_component(obj, config): | ||||
|     if CONF_UNIT_OF_MEASUREMENT in config: | ||||
|         add(obj.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) | ||||
|     if CONF_ICON in config: | ||||
|         add(obj.set_icon(config[CONF_ICON])) | ||||
|     if CONF_ACCURACY_DECIMALS in config: | ||||
|         add(obj.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) | ||||
|     if CONF_EXPIRE_AFTER in config: | ||||
|         if config[CONF_EXPIRE_AFTER] is None: | ||||
|             add(obj.disable_expire_after()) | ||||
|         else: | ||||
|             add(obj.set_expire_after(config[CONF_EXPIRE_AFTER])) | ||||
|     if CONF_FILTERS in config: | ||||
|         filters = [setup_filter(x) for x in config[CONF_FILTERS]] | ||||
|         add(obj.set_filters(ArrayInitializer(*filters))) | ||||
|     setup_mqtt_component(obj, config) | ||||
|  | ||||
|  | ||||
| def make_mqtt_sensor_for(exp, config): | ||||
|     rhs = App.make_mqtt_sensor_for(exp, config[CONF_NAME]) | ||||
|     mqtt_sensor = Pvariable('sensor::MQTTSensorComponent', config[CONF_ID], rhs) | ||||
|     setup_mqtt_sensor_component(mqtt_sensor, config) | ||||
							
								
								
									
										35
									
								
								esphomeyaml/components/sensor/adc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphomeyaml/components/sensor/adc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.const import CONF_ATTENUATION, CONF_ID, CONF_NAME, CONF_PIN, \ | ||||
|     CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, RawExpression, add, variable | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     '0db': 'ADC_0db', | ||||
|     '2.5db': 'ADC_2_5db', | ||||
|     '6db': 'ADC_6db', | ||||
|     '11db': 'ADC_11db', | ||||
| } | ||||
|  | ||||
| ATTENUATION_MODE_SCHEMA = vol.Any(*list(ATTENUATION_MODES.keys())) | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('adc'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.analog_pin, | ||||
|     vol.Optional(CONF_ATTENUATION): vol.All(cv.only_on_esp32, ATTENUATION_MODE_SCHEMA), | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_adc_sensor(config[CONF_PIN], config[CONF_NAME], | ||||
|                               config.get(CONF_UPDATE_INTERVAL)) | ||||
|     make = variable('Application::MakeADCSensor', config[CONF_ID], rhs) | ||||
|     adc = make.Padc | ||||
|     if CONF_ATTENUATION in config: | ||||
|         attenuation = ATTENUATION_MODES[config[CONF_ATTENUATION]] | ||||
|         add(adc.set_attenuation(RawExpression(attenuation))) | ||||
|     sensor.setup_mqtt_sensor_component(make.Pmqtt, config) | ||||
							
								
								
									
										56
									
								
								esphomeyaml/components/sensor/ads1115.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphomeyaml/components/sensor/ads1115.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.const import CONF_ADS1115_ID, CONF_GAIN, CONF_MULTIPLEXER, CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import get_variable, RawExpression | ||||
|  | ||||
| DEPENDENCIES = ['ads1115'] | ||||
|  | ||||
| MUX = { | ||||
|     'A0_A1': 'ADS1115_MUX_P0_N1', | ||||
|     'A0_A3': 'ADS1115_MUX_P0_N3', | ||||
|     'A1_A3': 'ADS1115_MUX_P1_N3', | ||||
|     'A2_A3': 'ADS1115_MUX_P2_N3', | ||||
|     'A0_GND': 'ADS1115_MUX_P0_NG', | ||||
|     'A1_GND': 'ADS1115_MUX_P1_NG', | ||||
|     'A2_GND': 'ADS1115_MUX_P2_NG', | ||||
|     'A3_GND': 'ADS1115_MUX_P3_NG', | ||||
| } | ||||
|  | ||||
| GAIN = { | ||||
|     '6.144': 'ADS1115_PGA_6P144', | ||||
|     '4.096': 'ADS1115_PGA_6P096', | ||||
|     '2.048': 'ADS1115_PGA_2P048', | ||||
|     '1.024': 'ADS1115_PGA_1P024', | ||||
|     '0.512': 'ADS1115_PGA_0P512', | ||||
|     '0.256': 'ADS1115_PGA_0P256', | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_gain(value): | ||||
|     if isinstance(value, float): | ||||
|         value = u'{:0.03f}'.format(value) | ||||
|     elif not isinstance(value, (str, unicode)): | ||||
|         raise vol.Invalid('invalid gain "{}"'.format(value)) | ||||
|  | ||||
|     if value not in GAIN: | ||||
|         raise vol.Invalid("Invalid gain, options are {}".format(', '.join(GAIN.keys()))) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Required(CONF_MULTIPLEXER): vol.All(vol.Upper, vol.Any(*list(MUX.keys()))), | ||||
|     vol.Required(CONF_GAIN): validate_gain, | ||||
|     vol.Optional(CONF_ADS1115_ID): cv.variable_id, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }).extend(sensor.MQTT_SENSOR_ID_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     hub = get_variable(config.get(CONF_ADS1115_ID), u'sensor::ADS1115Component') | ||||
|  | ||||
|     mux = RawExpression(MUX[config[CONF_MULTIPLEXER]]) | ||||
|     gain = RawExpression(GAIN[config[CONF_GAIN]]) | ||||
|     sensor_ = hub.get_sensor(mux, gain, config.get(CONF_UPDATE_INTERVAL)) | ||||
|     sensor.make_mqtt_sensor_for(sensor_, config) | ||||
							
								
								
									
										29
									
								
								esphomeyaml/components/sensor/bmp085.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphomeyaml/components/sensor/bmp085.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA | ||||
| from esphomeyaml.const import CONF_ADDRESS, CONF_ID, CONF_NAME, \ | ||||
|     CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, HexIntLiteral, add, variable | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('bmp085_sensor'): cv.register_variable_id, | ||||
|     vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Required(CONF_PRESSURE): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Optional(CONF_ADDRESS): cv.i2c_address, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_bmp085_sensor(config[CONF_TEMPERATURE][CONF_NAME], | ||||
|                                  config[CONF_PRESSURE][CONF_NAME], | ||||
|                                  config.get(CONF_UPDATE_INTERVAL)) | ||||
|     bmp = variable('Application::MakeBMP085Component', config[CONF_ID], rhs) | ||||
|     if CONF_ADDRESS in config: | ||||
|         add(bmp.Pbmp.set_address(HexIntLiteral(config[CONF_ADDRESS]))) | ||||
|     sensor.setup_mqtt_sensor_component(bmp.Pmqtt_temperature, config[CONF_TEMPERATURE]) | ||||
|     sensor.setup_mqtt_sensor_component(bmp.Pmqtt_pressure, config[CONF_PRESSURE]) | ||||
							
								
								
									
										31
									
								
								esphomeyaml/components/sensor/dallas.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphomeyaml/components/sensor/dallas.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.components.dallas import DALLAS_COMPONENT_CLASS | ||||
| from esphomeyaml.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_RESOLUTION, \ | ||||
|     CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import HexIntLiteral, get_variable | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     vol.Exclusive(CONF_ADDRESS, 'dallas'): cv.hex_int, | ||||
|     vol.Exclusive(CONF_INDEX, 'dallas'): cv.positive_int, | ||||
|     vol.Optional(CONF_DALLAS_ID): cv.variable_id, | ||||
|     vol.Optional(CONF_RESOLUTION): vol.All(vol.Coerce(int), vol.Range(min=8, max=12)), | ||||
| }).extend(sensor.MQTT_SENSOR_ID_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     hub = get_variable(config.get(CONF_DALLAS_ID), DALLAS_COMPONENT_CLASS) | ||||
|     update_interval = config.get(CONF_UPDATE_INTERVAL) | ||||
|     if CONF_RESOLUTION in config and update_interval is None: | ||||
|         update_interval = 10000 | ||||
|  | ||||
|     if CONF_ADDRESS in config: | ||||
|         address = HexIntLiteral(config[CONF_ADDRESS]) | ||||
|         sensor_ = hub.Pget_sensor_by_address(address, update_interval, | ||||
|                                              config.get(CONF_RESOLUTION)) | ||||
|     else: | ||||
|         sensor_ = hub.Pget_sensor_by_index(config[CONF_INDEX], update_interval, | ||||
|                                            config.get(CONF_RESOLUTION)) | ||||
|     sensor.make_mqtt_sensor_for(sensor_, config) | ||||
							
								
								
									
										31
									
								
								esphomeyaml/components/sensor/dht.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphomeyaml/components/sensor/dht.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA | ||||
| from esphomeyaml.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_NAME, CONF_PIN, \ | ||||
|     CONF_TEMPERATURE, CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, RawExpression, add, variable | ||||
|  | ||||
| DHT_MODELS = ['AUTO_DETECT', 'DHT11', 'DHT22', 'AM2302', 'RHT03'] | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('dht_sensor'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.input_output_pin, | ||||
|     vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Optional(CONF_MODEL): vol.All(vol.Upper, vol.Any(*DHT_MODELS)), | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_dht_sensor(config[CONF_PIN], config[CONF_TEMPERATURE][CONF_NAME], | ||||
|                               config[CONF_HUMIDITY][CONF_NAME], config.get(CONF_UPDATE_INTERVAL)) | ||||
|     dht = variable('Application::MakeDHTComponent', config[CONF_ID], rhs) | ||||
|     if CONF_MODEL in config: | ||||
|         model = RawExpression('DHT::{}'.format(config[CONF_MODEL])) | ||||
|         add(dht.Pdht.set_dht_model(model)) | ||||
|     sensor.setup_mqtt_sensor_component(dht.Pmqtt_temperature, config[CONF_TEMPERATURE]) | ||||
|     sensor.setup_mqtt_sensor_component(dht.Pmqtt_humidity, config[CONF_HUMIDITY]) | ||||
							
								
								
									
										26
									
								
								esphomeyaml/components/sensor/hdc1080.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphomeyaml/components/sensor/hdc1080.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA | ||||
| from esphomeyaml.const import CONF_HUMIDITY, CONF_ID, CONF_NAME, CONF_TEMPERATURE, \ | ||||
|     CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, variable | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('dht_sensor'): cv.register_variable_id, | ||||
|     vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_hdc1080_sensor(config[CONF_TEMPERATURE][CONF_NAME], | ||||
|                                   config[CONF_HUMIDITY][CONF_NAME], | ||||
|                                   config.get(CONF_UPDATE_INTERVAL)) | ||||
|     hdc1080 = variable('Application::MakeHDC1080Component', config[CONF_ID], rhs) | ||||
|     sensor.setup_mqtt_sensor_component(hdc1080.Pmqtt_temperature, config[CONF_TEMPERATURE]) | ||||
|     sensor.setup_mqtt_sensor_component(hdc1080.Pmqtt_humidity, config[CONF_HUMIDITY]) | ||||
							
								
								
									
										26
									
								
								esphomeyaml/components/sensor/htu21d.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphomeyaml/components/sensor/htu21d.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA | ||||
| from esphomeyaml.const import CONF_HUMIDITY, CONF_ID, CONF_NAME, CONF_TEMPERATURE, \ | ||||
|     CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, variable | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('htu21d'): cv.register_variable_id, | ||||
|     vol.Required(CONF_TEMPERATURE): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Required(CONF_HUMIDITY): MQTT_SENSOR_SCHEMA, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_htu21d_sensor(config[CONF_TEMPERATURE][CONF_NAME], | ||||
|                                  config[CONF_HUMIDITY][CONF_NAME], | ||||
|                                  config.get(CONF_UPDATE_INTERVAL)) | ||||
|     htu21d = variable('Application::MakeHTU21DComponent', config[CONF_ID], rhs) | ||||
|     sensor.setup_mqtt_sensor_component(htu21d.Pmqtt_temperature, config[CONF_TEMPERATURE]) | ||||
|     sensor.setup_mqtt_sensor_component(htu21d.Pmqtt_humidity, config[CONF_HUMIDITY]) | ||||
							
								
								
									
										58
									
								
								esphomeyaml/components/sensor/pulse_counter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphomeyaml/components/sensor/pulse_counter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.const import CONF_COUNT_MODE, CONF_FALLING_EDGE, CONF_ID, CONF_INTERNAL_FILTER, \ | ||||
|     CONF_NAME, CONF_PIN, CONF_PULL_MODE, CONF_RISING_EDGE, CONF_UPDATE_INTERVAL, \ | ||||
|     ESP_PLATFORM_ESP32 | ||||
| from esphomeyaml.helpers import App, RawExpression, add, variable | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32] | ||||
|  | ||||
| GPIO_PULL_MODES = { | ||||
|     'PULLUP': 'GPIO_PULLUP_ONLY', | ||||
|     'PULLDOWN': 'GPIO_PULLDOWN_ONLY', | ||||
|     'PULLUP_PULLDOWN': 'GPIO_PULLUP_PULLDOWN', | ||||
|     'FLOATING': 'GPIO_FLOATING', | ||||
| } | ||||
|  | ||||
| GPIO_PULL_MODE_SCHEMA = vol.All(vol.Upper, vol.Any(*list(GPIO_PULL_MODES.keys()))) | ||||
|  | ||||
| COUNT_MODES = { | ||||
|     'DISABLE': 'PCNT_COUNT_DIS', | ||||
|     'INCREMENT': 'PCNT_COUNT_INC', | ||||
|     'DECREMENT': 'PCNT_COUNT_DEC', | ||||
| } | ||||
|  | ||||
| COUNT_MODE_SCHEMA = vol.All(vol.Upper, vol.Any(*list(COUNT_MODES.keys()))) | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('pulse_counter'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.input_pin, | ||||
|     vol.Optional(CONF_PULL_MODE): GPIO_PULL_MODE_SCHEMA, | ||||
|     vol.Optional(CONF_COUNT_MODE): vol.Schema({ | ||||
|         vol.Required(CONF_RISING_EDGE): COUNT_MODE_SCHEMA, | ||||
|         vol.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA, | ||||
|     }), | ||||
|     vol.Optional(CONF_INTERNAL_FILTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=1023)), | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_pulse_counter_sensor(config[CONF_PIN], config[CONF_NAME], | ||||
|                                         config.get(CONF_UPDATE_INTERVAL)) | ||||
|     make = variable('Application::MakePulseCounter', config[CONF_ID], rhs) | ||||
|     pcnt = make.Ppcnt | ||||
|     if CONF_PULL_MODE in config: | ||||
|         pull_mode = GPIO_PULL_MODES[config[CONF_PULL_MODE]] | ||||
|         add(pcnt.set_pull_mode(RawExpression(pull_mode))) | ||||
|     if CONF_COUNT_MODE in config: | ||||
|         count_mode = config[CONF_COUNT_MODE] | ||||
|         rising_edge = COUNT_MODES[count_mode[CONF_RISING_EDGE]] | ||||
|         falling_edge = COUNT_MODES[count_mode[CONF_FALLING_EDGE]] | ||||
|         add(pcnt.set_edge_mode(RawExpression(rising_edge), RawExpression(falling_edge))) | ||||
|     if CONF_INTERNAL_FILTER in config: | ||||
|         add(pcnt.set_filter(config[CONF_INTERNAL_FILTER])) | ||||
|     sensor.setup_mqtt_sensor_component(make.Pmqtt, config) | ||||
							
								
								
									
										32
									
								
								esphomeyaml/components/sensor/ultrasonic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphomeyaml/components/sensor/ultrasonic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import sensor | ||||
| from esphomeyaml.const import CONF_ECHO_PIN, CONF_ID, CONF_NAME, \ | ||||
|     CONF_TIMEOUT_METER, CONF_TIMEOUT_TIME, CONF_TRIGGER_PIN, CONF_UPDATE_INTERVAL | ||||
| from esphomeyaml.helpers import App, add, exp_gpio_input_pin, exp_gpio_output_pin, \ | ||||
|     variable | ||||
|  | ||||
| PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('ultrasonic'): cv.register_variable_id, | ||||
|     vol.Required(CONF_TRIGGER_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
|     vol.Required(CONF_ECHO_PIN): pins.GPIO_INPUT_PIN_SCHEMA, | ||||
|     vol.Exclusive(CONF_TIMEOUT_METER, 'timeout'): cv.positive_float, | ||||
|     vol.Exclusive(CONF_TIMEOUT_TIME, 'timeout'): cv.positive_int, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_not_null_time_period, | ||||
| }).extend(sensor.MQTT_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     trigger = exp_gpio_output_pin(config[CONF_TRIGGER_PIN]) | ||||
|     echo = exp_gpio_input_pin(config[CONF_ECHO_PIN]) | ||||
|     rhs = App.make_ultrasonic_sensor(trigger, echo, config[CONF_NAME], | ||||
|                                      config.get(CONF_UPDATE_INTERVAL)) | ||||
|     make = variable('Application::MakeUltrasonicSensor', config[CONF_ID], rhs) | ||||
|     ultrasonic = make.Pultrasonic | ||||
|     if CONF_TIMEOUT_TIME in config: | ||||
|         add(ultrasonic.set_timeout_us(config[CONF_TIMEOUT_TIME])) | ||||
|     elif CONF_TIMEOUT_METER in config: | ||||
|         add(ultrasonic.set_timeout_m(config[CONF_TIMEOUT_METER])) | ||||
|     sensor.setup_mqtt_sensor_component(make.Pmqtt, config) | ||||
							
								
								
									
										25
									
								
								esphomeyaml/components/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphomeyaml/components/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_ICON, CONF_ID, CONF_NAME | ||||
| from esphomeyaml.helpers import App, Pvariable, add, setup_mqtt_component | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
|  | ||||
| }) | ||||
|  | ||||
| MQTT_SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_ICON): cv.icon, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def setup_mqtt_switch(obj, config): | ||||
|     if CONF_ICON in config: | ||||
|         add(obj.set_icon(config[CONF_ICON])) | ||||
|     setup_mqtt_component(obj, config) | ||||
|  | ||||
|  | ||||
| def make_mqtt_switch_for(exp, config): | ||||
|     rhs = App.make_mqtt_switch_for(exp, config[CONF_NAME]) | ||||
|     mqtt_switch = Pvariable('switch_::MQTTSwitchComponent', config[CONF_ID], rhs) | ||||
|     setup_mqtt_switch(mqtt_switch, config) | ||||
							
								
								
									
										18
									
								
								esphomeyaml/components/switch/gpio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphomeyaml/components/switch/gpio.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import pins | ||||
| from esphomeyaml.components import switch | ||||
| from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_PIN | ||||
| from esphomeyaml.helpers import App, exp_gpio_output_pin, variable | ||||
|  | ||||
| PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('gpio_switch'): cv.register_variable_id, | ||||
|     vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, | ||||
| }).extend(switch.MQTT_SWITCH_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_gpio_switch(exp_gpio_output_pin(config[CONF_PIN]), config[CONF_NAME]) | ||||
|     gpio = variable('Application::GPIOSwitchStruct', config[CONF_ID], rhs) | ||||
|     switch.setup_mqtt_switch(gpio.Pmqtt, config) | ||||
							
								
								
									
										87
									
								
								esphomeyaml/components/switch/ir_transmitter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphomeyaml/components/switch/ir_transmitter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import switch | ||||
| from esphomeyaml.components.ir_transmitter import IR_TRANSMITTER_COMPONENT_CLASS | ||||
| from esphomeyaml.const import CONF_ADDRESS, CONF_COMMAND, CONF_DATA, CONF_IR_TRANSMITTER_ID, \ | ||||
|     CONF_LG, CONF_NBITS, CONF_NEC, CONF_PANASONIC, CONF_REPEAT, CONF_SONY, CONF_TIMES, \ | ||||
|     CONF_WAIT_TIME_US, CONF_RAW, CONF_CARRIER_FREQUENCY | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
| from esphomeyaml.helpers import HexIntLiteral, MockObj, get_variable, ArrayInitializer | ||||
|  | ||||
| PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('ir_transmitter'): cv.register_variable_id, | ||||
|     vol.Exclusive(CONF_NEC, 'code'): vol.Schema({ | ||||
|         vol.Required(CONF_ADDRESS): cv.hex_uint16_t, | ||||
|         vol.Required(CONF_COMMAND): cv.hex_uint16_t, | ||||
|     }), | ||||
|     vol.Exclusive(CONF_LG, 'code'): vol.Schema({ | ||||
|         vol.Required(CONF_DATA): cv.hex_uint32_t, | ||||
|         vol.Optional(CONF_NBITS, default=28): vol.All(vol.Coerce(int), vol.Range(min=0, max=32)), | ||||
|     }), | ||||
|     vol.Exclusive(CONF_SONY, 'code'): vol.Schema({ | ||||
|         vol.Required(CONF_DATA): cv.hex_uint32_t, | ||||
|         vol.Optional(CONF_NBITS, default=12): vol.All(vol.Coerce(int), vol.Range(min=0, max=32)), | ||||
|     }), | ||||
|     vol.Exclusive(CONF_PANASONIC, 'code'): vol.Schema({ | ||||
|         vol.Required(CONF_ADDRESS): cv.hex_uint16_t, | ||||
|         vol.Required(CONF_COMMAND): cv.hex_uint32_t, | ||||
|     }), | ||||
|     vol.Exclusive(CONF_RAW, 'code'): vol.Schema({ | ||||
|         vol.Required(CONF_CARRIER_FREQUENCY): vol.All(cv.frequency, vol.Coerce(int)), | ||||
|         vol.Required(CONF_DATA): [vol.Coerce(int)], | ||||
|     }), | ||||
|     vol.Optional(CONF_REPEAT): vol.Any(cv.positive_not_null_int, vol.Schema({ | ||||
|         vol.Required(CONF_TIMES): cv.positive_not_null_int, | ||||
|         vol.Required(CONF_WAIT_TIME_US): cv.uint32_t, | ||||
|     })), | ||||
|     vol.Optional(CONF_IR_TRANSMITTER_ID): cv.variable_id, | ||||
| }).extend(switch.MQTT_SWITCH_SCHEMA.schema) | ||||
|  | ||||
| SendData = MockObj('switch_::ir::SendData', '::') | ||||
|  | ||||
|  | ||||
| def safe_hex(value): | ||||
|     if value is None: | ||||
|         return None | ||||
|     return HexIntLiteral(value) | ||||
|  | ||||
|  | ||||
| def exp_send_data(config): | ||||
|     if CONF_NEC in config: | ||||
|         conf = config[CONF_NEC] | ||||
|         base = SendData.from_nec(safe_hex(conf[CONF_ADDRESS]), | ||||
|                                  safe_hex(conf[CONF_COMMAND])) | ||||
|     elif CONF_LG in config: | ||||
|         conf = config[CONF_LG] | ||||
|         base = SendData.from_lg(safe_hex(conf[CONF_DATA]), conf.get(CONF_NBITS)) | ||||
|     elif CONF_SONY in config: | ||||
|         conf = config[CONF_SONY] | ||||
|         base = SendData.from_sony(safe_hex(conf[CONF_DATA]), conf.get(CONF_NBITS)) | ||||
|     elif CONF_PANASONIC in config: | ||||
|         conf = config[CONF_PANASONIC] | ||||
|         base = SendData.from_panasonic(safe_hex(conf[CONF_ADDRESS]), | ||||
|                                        safe_hex(conf[CONF_COMMAND])) | ||||
|     elif CONF_RAW in config: | ||||
|         conf = config[CONF_RAW] | ||||
|         data = ArrayInitializer(*conf[CONF_DATA]) | ||||
|         base = SendData.from_raw(data, conf[CONF_CARRIER_FREQUENCY]) | ||||
|     else: | ||||
|         raise ESPHomeYAMLError(u"Unsupported IR mode {}".format(config)) | ||||
|  | ||||
|     if CONF_REPEAT in config: | ||||
|         if isinstance(config[CONF_REPEAT], int): | ||||
|             times = config[CONF_REPEAT] | ||||
|             wait_us = None | ||||
|         else: | ||||
|             times = config[CONF_REPEAT][CONF_TIMES] | ||||
|             wait_us = config[CONF_REPEAT][CONF_WAIT_TIME_US] | ||||
|         base = MockObj(unicode(base), u'.') | ||||
|         base = base.repeat(times, wait_us) | ||||
|     return base | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     ir = get_variable(config.get(CONF_IR_TRANSMITTER_ID), IR_TRANSMITTER_COMPONENT_CLASS) | ||||
|     send_data = exp_send_data(config) | ||||
|     switch.make_mqtt_switch_for(ir.create_transmitter(send_data), config) | ||||
							
								
								
									
										14
									
								
								esphomeyaml/components/switch/restart.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphomeyaml/components/switch/restart.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import switch | ||||
| from esphomeyaml.const import CONF_ID, CONF_NAME | ||||
| from esphomeyaml.helpers import App, Pvariable | ||||
|  | ||||
| PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID('restart_switch'): cv.register_variable_id, | ||||
| }).extend(switch.MQTT_SWITCH_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_restart_switch(config[CONF_NAME]) | ||||
|     mqtt = Pvariable('switch_::MQTTSwitchComponent', config[CONF_ID], rhs) | ||||
|     switch.setup_mqtt_switch(mqtt, config) | ||||
							
								
								
									
										46
									
								
								esphomeyaml/components/wifi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphomeyaml/components/wifi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import CONF_DNS1, CONF_DNS2, CONF_GATEWAY, CONF_HOSTNAME, CONF_ID, \ | ||||
|     CONF_MANUAL_IP, CONF_PASSWORD, CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, CONF_WIFI | ||||
| from esphomeyaml.helpers import App, MockObj, Pvariable, StructInitializer, add | ||||
|  | ||||
| CONFIG_SCHEMA = cv.ID_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_WIFI): cv.register_variable_id, | ||||
|     vol.Required(CONF_SSID): cv.ssid, | ||||
|     vol.Optional(CONF_PASSWORD): cv.string, | ||||
|     vol.Optional(CONF_MANUAL_IP): vol.Schema({ | ||||
|         vol.Required(CONF_STATIC_IP): cv.ipv4, | ||||
|         vol.Required(CONF_GATEWAY): cv.ipv4, | ||||
|         vol.Required(CONF_SUBNET): cv.ipv4, | ||||
|         vol.Inclusive(CONF_DNS1, 'dns'): cv.ipv4, | ||||
|         vol.Inclusive(CONF_DNS2, 'dns'): cv.ipv4, | ||||
|     }), | ||||
|     vol.Optional(CONF_HOSTNAME): cv.hostname, | ||||
| }) | ||||
|  | ||||
| IPAddress = MockObj('IPAddress') | ||||
|  | ||||
|  | ||||
| def safe_ip(ip): | ||||
|     if ip is None: | ||||
|         return None | ||||
|     return IPAddress(*ip.args) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.init_wifi(config[CONF_SSID], config.get(CONF_PASSWORD)) | ||||
|     wifi = Pvariable('WiFiComponent', config[CONF_ID], rhs) | ||||
|     if CONF_MANUAL_IP in config: | ||||
|         manual_ip = config[CONF_MANUAL_IP] | ||||
|         exp = StructInitializer( | ||||
|             'ManualIP', | ||||
|             ('static_ip', safe_ip(manual_ip[CONF_STATIC_IP])), | ||||
|             ('gateway', safe_ip(manual_ip[CONF_GATEWAY])), | ||||
|             ('subnet', safe_ip(manual_ip[CONF_SUBNET])), | ||||
|             ('dns1', safe_ip(manual_ip.get(CONF_DNS1))), | ||||
|             ('dns2', safe_ip(manual_ip.get(CONF_DNS2))), | ||||
|         ) | ||||
|         add(wifi.set_manual_ip(exp)) | ||||
|     if CONF_HOSTNAME in config: | ||||
|         add(wifi.set_hostname(config[CONF_HOSTNAME])) | ||||
							
								
								
									
										280
									
								
								esphomeyaml/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								esphomeyaml/config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import importlib | ||||
| import logging | ||||
| from collections import OrderedDict | ||||
|  | ||||
| import voluptuous as vol | ||||
| from voluptuous.humanize import humanize_error | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml import helpers, yaml_util | ||||
| from esphomeyaml.const import CONF_BOARD, CONF_ESPHOMEYAML, CONF_LIBRARY_URI, CONF_MQTT, \ | ||||
|     CONF_NAME, \ | ||||
|     CONF_PLATFORM, CONF_SIMPLIFY, CONF_WIFI, ESP_PLATFORMS, ESP_PLATFORM_ESP32, \ | ||||
|     ESP_PLATFORM_ESP8266 | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
| from esphomeyaml.helpers import App, add, add_task, color | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| DEFAULT_LIBRARY_URI = u'esphomelib' | ||||
|  | ||||
| CORE_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_NAME): cv.valid_name, | ||||
|     vol.Required(CONF_PLATFORM): vol.All( | ||||
|         vol.Upper, vol.Any(ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266)), | ||||
|     vol.Required(CONF_BOARD): cv.string, | ||||
|     vol.Optional(CONF_LIBRARY_URI, default=DEFAULT_LIBRARY_URI): cv.string, | ||||
|     vol.Optional(CONF_SIMPLIFY, default=True): cv.boolean, | ||||
| }) | ||||
|  | ||||
| REQUIRED_COMPONENTS = [ | ||||
|     CONF_ESPHOMEYAML, CONF_WIFI, CONF_MQTT | ||||
| ] | ||||
|  | ||||
| _COMPONENT_CACHE = {} | ||||
| _ALL_COMPONENTS = [] | ||||
|  | ||||
|  | ||||
| def core_to_code(config): | ||||
|     add(App.set_name(config[CONF_NAME])) | ||||
|  | ||||
|  | ||||
| def get_component(domain): | ||||
|     if domain in _COMPONENT_CACHE: | ||||
|         return _COMPONENT_CACHE[domain] | ||||
|  | ||||
|     path = 'esphomeyaml.components.{}'.format(domain) | ||||
|     try: | ||||
|         module = importlib.import_module(path) | ||||
|     except ImportError as err: | ||||
|         _LOGGER.debug(err) | ||||
|         pass | ||||
|     else: | ||||
|         _COMPONENT_CACHE[domain] = module | ||||
|         return module | ||||
|  | ||||
|     _LOGGER.error("Unable to find component %s", domain) | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def get_platform(domain, platform): | ||||
|     return get_component("{}.{}".format(domain, platform)) | ||||
|  | ||||
|  | ||||
| def is_platform_component(component): | ||||
|     return hasattr(component, 'PLATFORM_SCHEMA') | ||||
|  | ||||
|  | ||||
| def validate_schema(config, schema): | ||||
|     return schema(config) | ||||
|  | ||||
|  | ||||
| class Config(OrderedDict): | ||||
|     def __init__(self): | ||||
|         super(Config, self).__init__() | ||||
|         self.errors = [] | ||||
|  | ||||
|     def add_error(self, message, domain=None, config=None): | ||||
|         if not isinstance(message, unicode): | ||||
|             message = unicode(message) | ||||
|         self.errors.append((message, domain, config)) | ||||
|  | ||||
|  | ||||
| def validate_config(config): | ||||
|     global _ALL_COMPONENTS | ||||
|  | ||||
|     for req in REQUIRED_COMPONENTS: | ||||
|         if req not in config: | ||||
|             raise ESPHomeYAMLError("Component %s is required for esphomeyaml.", req) | ||||
|  | ||||
|     _ALL_COMPONENTS = list(config.keys()) | ||||
|  | ||||
|     result = Config() | ||||
|  | ||||
|     def _comp_error(ex, domain, config): | ||||
|         result.add_error(_format_config_error(ex, domain, config), domain, config) | ||||
|  | ||||
|     try: | ||||
|         result[CONF_ESPHOMEYAML] = validate_schema(config[CONF_ESPHOMEYAML], CORE_SCHEMA) | ||||
|     except vol.Invalid as ex: | ||||
|         _comp_error(ex, CONF_ESPHOMEYAML, config) | ||||
|  | ||||
|     for domain, conf in config.iteritems(): | ||||
|         if domain == CONF_ESPHOMEYAML: | ||||
|             continue | ||||
|         if conf is None: | ||||
|             conf = {} | ||||
|         component = get_component(domain) | ||||
|         if component is None: | ||||
|             result.add_error(u"Component not found: {}".format(domain)) | ||||
|             continue | ||||
|  | ||||
|         esp_platforms = getattr(component, 'ESP_PLATFORMS', ESP_PLATFORMS) | ||||
|         if cv.ESP_PLATFORM not in esp_platforms: | ||||
|             result.add_error(u"Component {} doesn't support {}.".format(domain, cv.ESP_PLATFORM)) | ||||
|             continue | ||||
|  | ||||
|         success = True | ||||
|         dependencies = getattr(component, 'DEPENDENCIES', []) | ||||
|         for dependency in dependencies: | ||||
|             if dependency not in _ALL_COMPONENTS: | ||||
|                 result.add_error(u"Component {} requires {}".format(domain, dependency)) | ||||
|                 success = False | ||||
|         if not success: | ||||
|             continue | ||||
|  | ||||
|         if hasattr(component, 'CONFIG_SCHEMA'): | ||||
|             try: | ||||
|                 validated = component.CONFIG_SCHEMA(conf) | ||||
|                 result[domain] = validated | ||||
|             except vol.Invalid as ex: | ||||
|                 _comp_error(ex, domain, config) | ||||
|                 continue | ||||
|  | ||||
|         if not hasattr(component, 'PLATFORM_SCHEMA'): | ||||
|             continue | ||||
|  | ||||
|         platforms = [] | ||||
|         for i, p_config in enumerate(conf): | ||||
|             if not isinstance(p_config, dict): | ||||
|                 result.add_error(u"Platform schemas mus have 'platform:' key") | ||||
|                 continue | ||||
|             p_name = p_config.get(u'platform') | ||||
|             if p_name is None: | ||||
|                 result.add_error(u"No platform specified for {}".format(domain)) | ||||
|                 continue | ||||
|             platform = get_platform(domain, p_name) | ||||
|             if platform is None: | ||||
|                 result.add_error(u"Platform not found: {}.{}") | ||||
|                 continue | ||||
|  | ||||
|             if hasattr(platform, u'PLATFORM_SCHEMA'): | ||||
|                 try: | ||||
|                     p_validated = platform.PLATFORM_SCHEMA(p_config) | ||||
|                 except vol.Invalid as ex: | ||||
|                     _comp_error(ex, u'{}.{}'.format(domain, p_name), p_config) | ||||
|                     continue | ||||
|                 platforms.append(p_validated) | ||||
|         result[domain] = platforms | ||||
|     return result | ||||
|  | ||||
|  | ||||
| REQUIRED = ['esphomeyaml', 'wifi', 'mqtt'] | ||||
|  | ||||
|  | ||||
| def _format_config_error(ex, domain, config): | ||||
|     message = u"Invalid config for [{}]: ".format(domain) | ||||
|     if u'extra keys not allowed' in ex.error_message: | ||||
|         message += u'[{}] is an invalid option for [{}]. Check: {}->{}.' \ | ||||
|             .format(ex.path[-1], domain, domain, | ||||
|                     u'->'.join(str(m) for m in ex.path)) | ||||
|     else: | ||||
|         message += u'{}.'.format(humanize_error(config, ex)) | ||||
|  | ||||
|     domain_config = config.get(domain, config) | ||||
|     message += u" (See {}, line {}). ".format( | ||||
|         getattr(domain_config, '__config_file__', '?'), | ||||
|         getattr(domain_config, '__line__', '?')) | ||||
|  | ||||
|     return message | ||||
|  | ||||
|  | ||||
| def load_config(path): | ||||
|     try: | ||||
|         config = yaml_util.load_yaml(path) | ||||
|     except OSError: | ||||
|         raise ESPHomeYAMLError(u"Could not read configuration file at {}".format(path)) | ||||
|  | ||||
|     esp_platform = unicode(config.get(CONF_ESPHOMEYAML, {}).get(CONF_PLATFORM, u"")) | ||||
|     esp_platform = esp_platform.upper() | ||||
|     if esp_platform not in (ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266): | ||||
|         raise ESPHomeYAMLError(u"Invalid ESP Platform {}".format(esp_platform)) | ||||
|     cv.ESP_PLATFORM = esp_platform | ||||
|     cv.BOARD = unicode(config.get(CONF_ESPHOMEYAML, {}).get(CONF_BOARD, u"")) | ||||
|     helpers.SIMPLIFY = cv.boolean(config.get(CONF_SIMPLIFY, True)) | ||||
|  | ||||
|     try: | ||||
|         result = validate_config(config) | ||||
|     except Exception as e: | ||||
|         print(u"Unexpected exception while reading configuration:") | ||||
|         raise | ||||
|  | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def add_platform_task(domain, config): | ||||
|     platform_ = config[CONF_PLATFORM] | ||||
|     platform = get_platform(domain, platform_) | ||||
|     if not hasattr(platform, 'to_code'): | ||||
|         raise ESPHomeYAMLError(u"Platform '{}.{}' doesn't have to_code.".format(domain, platform_)) | ||||
|     add_task(platform.to_code, config) | ||||
|  | ||||
|  | ||||
| def add_component_task(domain, config): | ||||
|     if domain == CONF_ESPHOMEYAML: | ||||
|         add_task(core_to_code, config) | ||||
|         return | ||||
|     component = get_component(domain) | ||||
|     if is_platform_component(component): | ||||
|         for conf in config: | ||||
|             add_platform_task(domain, conf) | ||||
|     else: | ||||
|         if not hasattr(component, 'to_code'): | ||||
|             raise ESPHomeYAMLError(u"Component '{}' doesn't have to_code.".format(domain)) | ||||
|         add_task(component.to_code, config) | ||||
|  | ||||
|  | ||||
| def line_info(obj, **kwargs): | ||||
|     """Display line config source.""" | ||||
|     if hasattr(obj, '__config_file__'): | ||||
|         return color('cyan', "[source {}:{}]" | ||||
|                      .format(obj.__config_file__, obj.__line__ or '?'), | ||||
|                      **kwargs) | ||||
|     return '?' | ||||
|  | ||||
|  | ||||
| def dump_dict(layer, indent_count=3, listi=False, **kwargs): | ||||
|     def sort_dict_key(val): | ||||
|         """Return the dict key for sorting.""" | ||||
|         key = str.lower(val[0]) | ||||
|         return '0' if key == 'platform' else key | ||||
|  | ||||
|     indent_str = indent_count * ' ' | ||||
|     if listi or isinstance(layer, list): | ||||
|         indent_str = indent_str[:-1] + '-' | ||||
|     if isinstance(layer, dict): | ||||
|         for key, value in sorted(layer.items(), key=sort_dict_key): | ||||
|             if isinstance(value, (dict, list)): | ||||
|                 print(indent_str, key + ':', line_info(value, **kwargs)) | ||||
|                 dump_dict(value, indent_count + 2) | ||||
|             else: | ||||
|                 print(indent_str, key + ':', value) | ||||
|             indent_str = indent_count * ' ' | ||||
|     if isinstance(layer, (list, tuple)): | ||||
|         for i in layer: | ||||
|             if isinstance(i, dict): | ||||
|                 dump_dict(i, indent_count + 2, True) | ||||
|             else: | ||||
|                 print(' ', indent_str, i) | ||||
|  | ||||
|  | ||||
| def read_config(path): | ||||
|     _LOGGER.debug("Reading configuration...") | ||||
|     res = load_config(path) | ||||
|     excepts = {} | ||||
|     for err in res.errors: | ||||
|         domain = err[1] or u"General Error" | ||||
|         excepts.setdefault(domain, []).append(err[0]) | ||||
|         if err[2] is not None: | ||||
|             excepts[domain].append(err[2]) | ||||
|  | ||||
|     if excepts: | ||||
|         print(color('bold_white', u"Failed config")) | ||||
|         for domain, config in excepts.iteritems(): | ||||
|             print(' ', color('bold_red', domain + ':'), color('red', '', reset='red')) | ||||
|             dump_dict(config, reset='red') | ||||
|             print(color('reset')) | ||||
|         return None | ||||
|     return dict(**res) | ||||
							
								
								
									
										372
									
								
								esphomeyaml/config_validation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								esphomeyaml/config_validation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,372 @@ | ||||
| # coding=utf-8 | ||||
| """Helpers for config validation using voluptuous.""" | ||||
| from __future__ import print_function | ||||
|  | ||||
| import logging | ||||
| from datetime import timedelta | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphomeyaml.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_ID, \ | ||||
|     CONF_NAME, CONF_PAYLOAD_AVAILABLE, \ | ||||
|     CONF_PAYLOAD_NOT_AVAILABLE, CONF_PLATFORM, CONF_RETAIN, CONF_STATE_TOPIC, CONF_TOPIC, \ | ||||
|     ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266 | ||||
| from esphomeyaml.core import HexInt, IPAddress | ||||
| from esphomeyaml.helpers import ensure_unique_string | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) | ||||
| positive_float = vol.All(vol.Coerce(float), vol.Range(min=0)) | ||||
| zero_to_one_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)), | ||||
| positive_int = vol.All(vol.Coerce(int), vol.Range(min=0)) | ||||
| positive_not_null_int = vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False)) | ||||
|  | ||||
| ESP_PLATFORM = '' | ||||
| BOARD = '' | ||||
|  | ||||
| ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' | ||||
|  | ||||
| RESERVED_IDS = [ | ||||
|     # C++ keywords http://en.cppreference.com/w/cpp/keyword | ||||
|     'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', | ||||
|     'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl', 'concept', 'const', | ||||
|     'constexpr', 'const_cast', 'continue', 'decltype', 'default', 'delete', 'do', 'double', | ||||
|     'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'export', 'extern', 'false', 'float', | ||||
|     'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', | ||||
|     'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', | ||||
|     'public', 'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed', 'sizeof', | ||||
|     'static', 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this', | ||||
|     'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', | ||||
|     'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq', | ||||
|  | ||||
|     'App', 'pinMode', 'delay', 'delayMicroseconds', 'digitalRead', 'digitalWrite', 'INPUT', | ||||
|     'OUTPUT', | ||||
| ] | ||||
|  | ||||
|  | ||||
| def alphanumeric(value): | ||||
|     if value is None: | ||||
|         raise vol.Invalid("string value is None") | ||||
|     value = unicode(value) | ||||
|     if not value.isalnum(): | ||||
|         raise vol.Invalid("string value is not alphanumeric") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def valid_name(value): | ||||
|     value = string_strict(value) | ||||
|     if not all(c in ALLOWED_NAME_CHARS for c in value): | ||||
|         raise vol.Invalid(u"Valid characters for name are %s", ALLOWED_NAME_CHARS) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def string(value): | ||||
|     if isinstance(value, (dict, list)): | ||||
|         raise vol.Invalid("string value cannot be dictionary or list.") | ||||
|     if value is not None: | ||||
|         return unicode(value) | ||||
|     raise vol.Invalid("string value is None") | ||||
|  | ||||
|  | ||||
| def string_strict(value): | ||||
|     """Strictly only allow strings.""" | ||||
|     if isinstance(value, str) or isinstance(value, unicode): | ||||
|         return value | ||||
|     raise vol.Invalid("Must be string, did you forget putting quotes " | ||||
|                       "around the value?") | ||||
|  | ||||
|  | ||||
| def icon(value): | ||||
|     """Validate icon.""" | ||||
|     value = string_strict(value) | ||||
|     if value.startswith('mdi:'): | ||||
|         return value | ||||
|     raise vol.Invalid('Icons should start with prefix "mdi:"') | ||||
|  | ||||
|  | ||||
| def boolean(value): | ||||
|     """Validate and coerce a boolean value.""" | ||||
|     if isinstance(value, str): | ||||
|         value = value.lower() | ||||
|         if value in ('1', 'true', 'yes', 'on', 'enable'): | ||||
|             return True | ||||
|         if value in ('0', 'false', 'no', 'off', 'disable'): | ||||
|             return False | ||||
|         raise vol.Invalid('invalid boolean value {}'.format(value)) | ||||
|     return bool(value) | ||||
|  | ||||
|  | ||||
| def ensure_list(value): | ||||
|     """Wrap value in list if it is not one.""" | ||||
|     if value is None: | ||||
|         return [] | ||||
|     if isinstance(value, list): | ||||
|         return value | ||||
|     return [value] | ||||
|  | ||||
|  | ||||
| def ensure_dict(value): | ||||
|     if value is None: | ||||
|         return {} | ||||
|     if not isinstance(value, dict): | ||||
|         raise vol.Invalid("Expected a dictionary") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def hex_int_(value): | ||||
|     if isinstance(value, (int, long)): | ||||
|         return HexInt(value) | ||||
|     value = string_strict(value).lower() | ||||
|     if value.startswith('0x'): | ||||
|         return HexInt(int(value, 16)) | ||||
|     return HexInt(int(value)) | ||||
|  | ||||
|  | ||||
| def int_(value): | ||||
|     if isinstance(value, (int, long)): | ||||
|         return value | ||||
|     value = string_strict(value).lower() | ||||
|     if value.startswith('0x'): | ||||
|         return int(value, 16) | ||||
|     return int(value) | ||||
|  | ||||
|  | ||||
| hex_int = vol.Coerce(hex_int_) | ||||
| match_cpp_var_ = vol.Match(r'^[a-zA-Z_][a-zA-Z0-9_]+$', msg=u"Must be a valid C++ variable name") | ||||
|  | ||||
|  | ||||
| def variable_id(value): | ||||
|     value = match_cpp_var_(value) | ||||
|     if value in RESERVED_IDS: | ||||
|         raise vol.Invalid(u"ID {} is reserved internally and cannot be used".format(value)) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def only_on(platforms): | ||||
|     if not isinstance(platforms, list): | ||||
|         platforms = [platforms] | ||||
|  | ||||
|     def validator_(obj): | ||||
|         print(obj) | ||||
|         if ESP_PLATFORM not in platforms: | ||||
|             raise vol.Invalid(u"This feature is only available on {}".format(platforms)) | ||||
|         return obj | ||||
|  | ||||
|     return validator_ | ||||
|  | ||||
|  | ||||
| only_on_esp32 = only_on(ESP_PLATFORM_ESP32) | ||||
| only_on_esp8266 = only_on(ESP_PLATFORM_ESP8266) | ||||
|  | ||||
|  | ||||
| # Adapted from: | ||||
| # https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666 | ||||
| def has_at_least_one_key(*keys): | ||||
|     """Validate that at least one key exists.""" | ||||
|  | ||||
|     def validate(obj): | ||||
|         """Test keys exist in dict.""" | ||||
|         if not isinstance(obj, dict): | ||||
|             raise vol.Invalid('expected dictionary') | ||||
|  | ||||
|         for k in obj.keys(): | ||||
|             if k in keys: | ||||
|                 return obj | ||||
|         raise vol.Invalid('must contain one of {}.'.format(', '.join(keys))) | ||||
|  | ||||
|     return validate | ||||
|  | ||||
|  | ||||
| TIME_PERIOD_ERROR = "Time period {} should be format 5ms, 5s, 5min, 5h" | ||||
|  | ||||
| time_period_dict = vol.All( | ||||
|     dict, vol.Schema({ | ||||
|         'days': vol.Coerce(int), | ||||
|         'hours': vol.Coerce(int), | ||||
|         'minutes': vol.Coerce(int), | ||||
|         'seconds': vol.Coerce(int), | ||||
|         'milliseconds': vol.Coerce(int), | ||||
|     }), | ||||
|     has_at_least_one_key('days', 'hours', 'minutes', | ||||
|                          'seconds', 'milliseconds'), | ||||
|     lambda value: timedelta(**value)) | ||||
|  | ||||
|  | ||||
| def time_period_str(value): | ||||
|     """Validate and transform time offset.""" | ||||
|     if isinstance(value, int): | ||||
|         raise vol.Invalid("Make sure you wrap time values in quotes") | ||||
|     elif not isinstance(value, (str, unicode)): | ||||
|         raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) | ||||
|  | ||||
|     value = unicode(value) | ||||
|     if value.endswith(u'ms'): | ||||
|         return vol.Coerce(int)(value[:-2]) | ||||
|     elif value.endswith(u's'): | ||||
|         return vol.Coerce(float)(value[:-1]) * 1000 | ||||
|     elif value.endswith(u'min'): | ||||
|         return vol.Coerce(float)(value[:-3]) * 1000 * 60 | ||||
|     elif value.endswith(u'h'): | ||||
|         return vol.Coerce(float)(value[:-1]) * 1000 * 60 * 60 | ||||
|     raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) | ||||
|  | ||||
|  | ||||
| def time_period_milliseconds(value): | ||||
|     try: | ||||
|         return timedelta(milliseconds=int(value)) | ||||
|     except (ValueError, TypeError): | ||||
|         raise vol.Invalid('Expected milliseconds, got {}'.format(value)) | ||||
|  | ||||
|  | ||||
| def time_period_to_milliseconds(value): | ||||
|     if isinstance(value, (int, long)): | ||||
|         return value | ||||
|     if isinstance(value, float): | ||||
|         return int(value) | ||||
|     return value / timedelta(milliseconds=1) | ||||
|  | ||||
|  | ||||
| time_period = vol.All(vol.Any(time_period_str, timedelta, time_period_dict, | ||||
|                               time_period_milliseconds), time_period_to_milliseconds) | ||||
| positive_time_period = vol.All(time_period, vol.Range(min=0)) | ||||
| positive_not_null_time_period = vol.All(time_period, vol.Range(min=0, min_included=False)) | ||||
|  | ||||
|  | ||||
| METRIC_SUFFIXES = { | ||||
|     'E': 1e18, 'P': 1e15, 'T': 1e12, 'G': 1e9, 'M': 1e6, 'k': 1e3, 'da': 10, 'd': 1e-1, | ||||
|     'c': 1e-2, 'm': 0.001, u'µ': 1e-6, 'u': 1e-6, 'n': 1e-9, 'p': 1e-12, 'f': 1e-15, 'a': 1e-18, | ||||
| } | ||||
|  | ||||
|  | ||||
| def frequency(value): | ||||
|     value = string(value).replace(' ', '').lower() | ||||
|     if value.endswith('Hz') or value.endswith('hz') or value.endswith('HZ'): | ||||
|         value = value[:-2] | ||||
|     if not value: | ||||
|         raise vol.Invalid(u"Frequency must have value") | ||||
|     multiplier = 1 | ||||
|     if value[:-1] in METRIC_SUFFIXES: | ||||
|         multiplier = METRIC_SUFFIXES[value[:-1]] | ||||
|         value = value[:-1] | ||||
|     elif len(value) >= 2 and value[:-2] in METRIC_SUFFIXES: | ||||
|         multiplier = METRIC_SUFFIXES[value[:-2]] | ||||
|         value = value[:-2] | ||||
|     float_val = vol.Coerce(float)(value) | ||||
|     return float_val * multiplier | ||||
|  | ||||
|  | ||||
| def hostname(value): | ||||
|     value = string(value) | ||||
|     if len(value) > 63: | ||||
|         raise vol.Invalid("Hostnames can only be 63 characters long") | ||||
|     for c in value: | ||||
|         if not (c.isalnum() or c in '_-'): | ||||
|             raise vol.Invalid("Hostname can only have alphanumeric characters and _ or -") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def ssid(value): | ||||
|     if value is None: | ||||
|         raise vol.Invalid("SSID can not be None") | ||||
|     if not isinstance(value, str): | ||||
|         raise vol.Invalid("SSID must be a string. Did you wrap it in quotes?") | ||||
|     if not value: | ||||
|         raise vol.Invalid("SSID can't be empty.") | ||||
|     if len(value) > 32: | ||||
|         raise vol.Invalid("SSID can't be longer than 32 characters") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def ipv4(value): | ||||
|     if isinstance(value, list): | ||||
|         parts = value | ||||
|     elif isinstance(value, str): | ||||
|         parts = value.split('.') | ||||
|     else: | ||||
|         raise vol.Invalid("IPv4 address must consist of either string or " | ||||
|                           "integer list") | ||||
|     if len(parts) != 4: | ||||
|         raise vol.Invalid("IPv4 address must consist of four point-separated " | ||||
|                           "integers") | ||||
|     parts_ = list(map(int, parts)) | ||||
|     if not all(0 <= x < 256 for x in parts_): | ||||
|         raise vol.Invalid("IPv4 address parts must be in range from 0 to 255") | ||||
|     return IPAddress(*parts_) | ||||
|  | ||||
|  | ||||
| def publish_topic(value): | ||||
|     value = string_strict(value) | ||||
|     if value.endswith('/'): | ||||
|         raise vol.Invalid("Publish topic can't end with '/'") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| subscribe_topic = string_strict  # TODO improve this | ||||
| mqtt_payload = string  # TODO improve this | ||||
| uint8_t = vol.All(int_, vol.Range(min=0, max=255)) | ||||
| uint16_t = vol.All(int_, vol.Range(min=0, max=65535)) | ||||
| uint32_t = vol.All(int_, vol.Range(min=0, max=4294967295)) | ||||
| hex_uint8_t = vol.All(hex_int, vol.Range(min=0, max=255)) | ||||
| hex_uint16_t = vol.All(hex_int, vol.Range(min=0, max=65535)) | ||||
| hex_uint32_t = vol.All(hex_int, vol.Range(min=0, max=4294967295)) | ||||
| i2c_address = hex_uint8_t | ||||
|  | ||||
|  | ||||
| def invalid(value): | ||||
|     raise vol.Invalid("This shouldn't happen.") | ||||
|  | ||||
|  | ||||
| def valid(value): | ||||
|     return value | ||||
|  | ||||
|  | ||||
| REGISTERED_IDS = set() | ||||
|  | ||||
|  | ||||
| def register_variable_id(value): | ||||
|     s = variable_id(value) | ||||
|     if s in REGISTERED_IDS: | ||||
|         raise vol.Invalid("This ID has already been used") | ||||
|     REGISTERED_IDS.add(s) | ||||
|     return s | ||||
|  | ||||
|  | ||||
| class GenerateID(vol.Optional): | ||||
|     def __init__(self, basename): | ||||
|         self._basename = basename | ||||
|         super(GenerateID, self).__init__(CONF_ID, default=self.default_variable_id) | ||||
|  | ||||
|     def default_variable_id(self): | ||||
|         return ensure_unique_string(self._basename, REGISTERED_IDS) | ||||
|  | ||||
|  | ||||
| ID_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_ID): invalid, | ||||
| }) | ||||
|  | ||||
| REQUIRED_ID_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_ID): register_variable_id, | ||||
| }) | ||||
|  | ||||
| PLATFORM_SCHEMA = ID_SCHEMA.extend({ | ||||
|     vol.Required(CONF_PLATFORM): valid, | ||||
| }) | ||||
|  | ||||
| MQTT_COMPONENT_AVAILABILITY_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_TOPIC): subscribe_topic, | ||||
|     vol.Optional(CONF_PAYLOAD_AVAILABLE, default='online'): mqtt_payload, | ||||
|     vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE, default='offline'): mqtt_payload, | ||||
| }) | ||||
|  | ||||
| MQTT_COMPONENT_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_NAME): string, | ||||
|     vol.Optional(CONF_RETAIN): boolean, | ||||
|     vol.Optional(CONF_DISCOVERY): boolean, | ||||
|     vol.Optional(CONF_STATE_TOPIC): publish_topic, | ||||
|     vol.Optional(CONF_AVAILABILITY): MQTT_COMPONENT_AVAILABILITY_SCHEMA, | ||||
| }) | ||||
|  | ||||
| MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend({ | ||||
|     vol.Optional(CONF_COMMAND_TOPIC): subscribe_topic, | ||||
| }) | ||||
							
								
								
									
										175
									
								
								esphomeyaml/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								esphomeyaml/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| """Constants used by esphomeyaml.""" | ||||
|  | ||||
| MAJOR_VERSION = 0 | ||||
| MINOR_VERSION = 1 | ||||
| PATCH_VERSION = '0' | ||||
| __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) | ||||
| __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) | ||||
|  | ||||
| ESP_PLATFORM_ESP32 = 'ESP32' | ||||
| ESP_PLATFORM_ESP8266 = 'ESP8266' | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] | ||||
|  | ||||
| APB_CLOCK_FREQ = 80000000 | ||||
|  | ||||
| CONF_ESPHOMEYAML = 'esphomeyaml' | ||||
| CONF_NAME = 'name' | ||||
| CONF_PLATFORM = 'platform' | ||||
| CONF_BOARD = 'board' | ||||
| CONF_SIMPLIFY = 'simplify' | ||||
| CONF_LIBRARY_URI = 'library_uri' | ||||
| CONF_LOGGER = 'logger' | ||||
| CONF_WIFI = 'wifi' | ||||
| CONF_SSID = 'ssid' | ||||
| CONF_PASSWORD = 'password' | ||||
| CONF_MANUAL_IP = 'manual_ip' | ||||
| CONF_STATIC_IP = 'static_ip' | ||||
| CONF_GATEWAY = 'gateway' | ||||
| CONF_SUBNET = 'subnet' | ||||
| CONF_OTA = 'ota' | ||||
| CONF_MQTT = 'mqtt' | ||||
| CONF_BROKER = 'broker' | ||||
| CONF_USERNAME = 'username' | ||||
| CONF_POWER_SUPPLY = 'power_supply' | ||||
| CONF_ID = 'id' | ||||
| CONF_PIN = 'pin' | ||||
| CONF_NUMBER = 'number' | ||||
| CONF_INVERTED = 'inverted' | ||||
| CONF_I2C = 'i2c' | ||||
| CONF_SDA = 'sda' | ||||
| CONF_SCL = 'scl' | ||||
| CONF_FREQUENCY = 'frequency' | ||||
| CONF_PCA9685 = 'pca9685' | ||||
| CONF_PCA9685_ID = 'pca9685_id' | ||||
| CONF_OUTPUT = 'output' | ||||
| CONF_CHANNEL = 'channel' | ||||
| CONF_LIGHT = 'light' | ||||
| CONF_RED = 'red' | ||||
| CONF_GREEN = 'green' | ||||
| CONF_BLUE = 'blue' | ||||
| CONF_SENSOR = 'sensor' | ||||
| CONF_TEMPERATURE = 'temperature' | ||||
| CONF_HUMIDITY = 'humidity' | ||||
| CONF_MODEL = 'model' | ||||
| CONF_BINARY_SENSOR = 'binary_sensor' | ||||
| CONF_DEVICE_CLASS = 'device_class' | ||||
| CONF_GPIO = 'gpio' | ||||
| CONF_DHT = 'dht' | ||||
| CONF_SAFE_MODE = 'safe_mode' | ||||
| CONF_MODE = 'mode' | ||||
| CONF_GAMMA_CORRECT = 'gamma_correct' | ||||
| CONF_RETAIN = 'retain' | ||||
| CONF_DISCOVERY = 'discovery' | ||||
| CONF_DISCOVERY_PREFIX = 'discovery_prefix' | ||||
| CONF_STATE_TOPIC = 'state_topic' | ||||
| CONF_COMMAND_TOPIC = 'command_topic' | ||||
| CONF_AVAILABILITY = 'availability' | ||||
| CONF_TOPIC = 'topic' | ||||
| CONF_PAYLOAD_AVAILABLE = 'payload_available' | ||||
| CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available' | ||||
| CONF_DEFAULT_TRANSITION_LENGTH = 'default_transition_length' | ||||
| CONF_BINARY = 'binary' | ||||
| CONF_WHITE = 'white' | ||||
| CONF_RGBW = 'rgbw' | ||||
| CONF_MAX_POWER = 'max_power' | ||||
| CONF_BIT_DEPTH = 'bit_depth' | ||||
| CONF_BAUD_RATE = 'baud_rate' | ||||
| CONF_LOG_TOPIC = 'log_topic' | ||||
| CONF_TX_BUFFER_SIZE = 'tx_buffer_size' | ||||
| CONF_LEVEL = 'level' | ||||
| CONF_LOGS = 'logs' | ||||
| CONF_PORT = 'port' | ||||
| CONF_WILL_MESSAGE = 'will_message' | ||||
| CONF_BIRTH_MESSAGE = 'birth_message' | ||||
| CONF_PAYLOAD = 'payload' | ||||
| CONF_QOS = 'qos' | ||||
| CONF_DISCOVERY_RETAIN = 'discovery_retain' | ||||
| CONF_TOPIC_PREFIX = 'topic_prefix' | ||||
| CONF_HOSTNAME = 'hostname' | ||||
| CONF_PHASE_BALANCER = 'phase_balancer' | ||||
| CONF_ADDRESS = 'address' | ||||
| CONF_ENABLE_TIME = 'enable_time' | ||||
| CONF_KEEP_ON_TIME = 'keep_on_time' | ||||
| CONF_DNS1 = 'dns1' | ||||
| CONF_DNS2 = 'dns2' | ||||
| CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement' | ||||
| CONF_ICON = 'icon' | ||||
| CONF_ACCURACY_DECIMALS = 'accuracy_decimals' | ||||
| CONF_EXPIRE_AFTER = 'expire_after' | ||||
| CONF_FILTERS = 'filters' | ||||
| CONF_OFFSET = 'offset' | ||||
| CONF_MULTIPLY = 'multiply' | ||||
| CONF_FILTER_OUT = 'filter_out' | ||||
| CONF_FILTER_NAN = 'filter_nan' | ||||
| CONF_SLIDING_WINDOW_MOVING_AVERAGE = 'sliding_window_moving_average' | ||||
| CONF_EXPONENTIAL_MOVING_AVERAGE = 'exponential_moving_average' | ||||
| CONF_WINDOW_SIZE = 'window_size' | ||||
| CONF_SEND_EVERY = 'send_every' | ||||
| CONF_ALPHA = 'alpha' | ||||
| CONF_LAMBDA = 'lambda' | ||||
| CONF_UPDATE_INTERVAL = 'update_interval' | ||||
| CONF_PULL_MODE = 'pull_mode' | ||||
| CONF_COUNT_MODE = 'count_mode' | ||||
| CONF_RISING_EDGE = 'rising_edge' | ||||
| CONF_FALLING_EDGE = 'falling_edge' | ||||
| CONF_INTERNAL_FILTER = 'internal_filter' | ||||
| CONF_DALLAS_ID = 'dallas_id' | ||||
| CONF_INDEX = 'index' | ||||
| CONF_RESOLUTION = 'resolution' | ||||
| CONF_ATTENUATION = 'attenuation' | ||||
| CONF_PRESSURE = 'pressure' | ||||
| CONF_TRIGGER_PIN = 'trigger_pin' | ||||
| CONF_ECHO_PIN = 'echo_pin' | ||||
| CONF_TIMEOUT_METER = 'timeout_meter' | ||||
| CONF_TIMEOUT_TIME = 'timeout_time' | ||||
| CONF_IR_TRANSMITTER_ID = 'ir_transmitter_id' | ||||
| CONF_CARRIER_DUTY_PERCENT = 'carrier_duty_percent' | ||||
| CONF_NEC = 'nec' | ||||
| CONF_COMMAND = 'command' | ||||
| CONF_DATA = 'data' | ||||
| CONF_NBITS = 'nbits' | ||||
| CONF_LG = 'lg' | ||||
| CONF_SONY = 'sony' | ||||
| CONF_PANASONIC = 'panasonic' | ||||
| CONF_REPEAT = 'repeat' | ||||
| CONF_TIMES = 'times' | ||||
| CONF_WAIT_TIME_US = 'wait_time_us' | ||||
| CONF_OSCILLATION_OUTPUT = 'oscillation_output' | ||||
| CONF_SPEED = 'speed' | ||||
| CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic' | ||||
| CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic' | ||||
| CONF_SPEED_STATE_TOPIC = 'speed_state_topic' | ||||
| CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic' | ||||
| CONF_LOW = 'low' | ||||
| CONF_MEDIUM = 'medium' | ||||
| CONF_HIGH = 'high' | ||||
| CONF_NUM_ATTEMPTS = 'num_attempts' | ||||
| CONF_CLIENT_ID = 'client_id' | ||||
| CONF_RAW = 'raw' | ||||
| CONF_CARRIER_FREQUENCY = 'carrier_frequency' | ||||
| CONF_RATE = 'rate' | ||||
| CONF_ADS1115_ID = 'ads1115_id' | ||||
| CONF_MULTIPLEXER = 'multiplexer' | ||||
| CONF_GAIN = 'gain' | ||||
|  | ||||
| ESP32_BOARDS = [ | ||||
|     'featheresp32', 'node32s', 'espea32', 'firebeetle32', 'esp32doit-devkit-v1', | ||||
|     'pocket_32', 'espectro32', 'esp32vn-iot-uno', 'esp320', 'esp-wrover-kit', | ||||
|     'esp32dev', 'heltec_wifi_kit32', 'heltec_wifi_lora_32', 'hornbill32dev', | ||||
|     'hornbill32minima', 'intorobot', 'm5stack-core-esp32', 'mhetesp32devkit', | ||||
|     'mhetesp32minikit', 'nano32', 'microduino-core-esp32', 'nodemcu-32s', | ||||
|     'quantum', 'esp32-evb', 'esp32-gateway', 'onehorse32dev', 'esp32thing', | ||||
|     'espino32', 'lolin32', 'wemosbat', 'widora-air', 'nina_w10', | ||||
| ] | ||||
|  | ||||
| ESP8266_BOARDS = [ | ||||
|     'gen4iod', 'huzzah', 'oak', 'espduino', 'espectro', 'espresso_lite_v1', | ||||
|     'espresso_lite_v2', 'espino', 'esp01', 'esp01_1m', 'esp07', 'esp12e', 'esp8285', | ||||
|     'esp_wroom_02', 'phoenix_v1', 'phoenix_v2', 'wifinfo', 'heltex_wifi_kit_8', | ||||
|     'nodemcu', 'nodemcuv2', 'modwifi', 'wio_node', 'sparkfunBlynk', 'thing', | ||||
|     'thingdev', 'esp210', 'espinotee', 'd1', 'd1_mini', 'd1_mini_lite', 'd1_mini_pro', | ||||
| ] | ||||
| ESP_BOARDS_FOR_PLATFORM = { | ||||
|     ESP_PLATFORM_ESP32: ESP32_BOARDS, | ||||
|     ESP_PLATFORM_ESP8266: ESP8266_BOARDS | ||||
| } | ||||
							
								
								
									
										18
									
								
								esphomeyaml/core.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphomeyaml/core.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| class ESPHomeYAMLError(Exception): | ||||
|     """General esphomeyaml exception occurred.""" | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class HexInt(long): | ||||
|     def __str__(self): | ||||
|         return "0x{:X}".format(self) | ||||
|  | ||||
|  | ||||
| class IPAddress(object): | ||||
|     def __init__(self, *args): | ||||
|         if len(args) != 4: | ||||
|             raise ValueError(u"IPAddress must consist up 4 items") | ||||
|         self.args = args | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '.'.join(str(x) for x in self.args) | ||||
							
								
								
									
										331
									
								
								esphomeyaml/espota.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										331
									
								
								esphomeyaml/espota.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,331 @@ | ||||
| #!/usr/bin/env python | ||||
| # | ||||
| # Copy of espota.py from ESP32 Arduino project with some modifications. | ||||
| # | ||||
| # Original espota.py by Ivan Grokhotkov: | ||||
| # https://gist.github.com/igrr/d35ab8446922179dc58c | ||||
| # | ||||
| # Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) | ||||
| # Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev) | ||||
| # Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman) | ||||
| # | ||||
| # This script will push an OTA update to the ESP | ||||
| # use it like: python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P | ||||
| # <Host_port> [-a password] -f <sketch.bin> | ||||
| # Or to upload SPIFFS image: | ||||
| # python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <HOST_port> [-a | ||||
| # password] -s -f <spiffs.bin> | ||||
| # | ||||
| # Changes | ||||
| # 2018-03-29: | ||||
| # - Clean up Code | ||||
| # - Merge from esptool for ESP8266 | ||||
| # 2015-09-18: | ||||
| # - Add option parser. | ||||
| # - Add logging | ||||
| # - Send command to controller to differ between flashing and transmitting SPIFFS image. | ||||
| # | ||||
| # Changes | ||||
| # 2015-11-09: | ||||
| # - Added digest authentication | ||||
| # - Enhanced error tracking and reporting | ||||
| # | ||||
| # Changes | ||||
| # 2016-01-03: | ||||
| # - Added more options to parser. | ||||
| # | ||||
|  | ||||
| import hashlib | ||||
| import logging | ||||
| import optparse | ||||
| import os | ||||
| import random | ||||
| import socket | ||||
| import sys | ||||
|  | ||||
| # Commands | ||||
| FLASH = 0 | ||||
| SPIFFS = 100 | ||||
| AUTH = 200 | ||||
| PROGRESS = False | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def update_progress(progress): | ||||
|     """Displays or updates a console progress bar | ||||
|  | ||||
|     Accepts a float between 0 and 1. Any int will be converted to a float. | ||||
|     A value under 0 represents a 'halt'. A value at 1 or bigger represents 100%. | ||||
|  | ||||
|     :param progress: | ||||
|     :return: | ||||
|     """ | ||||
|     if PROGRESS: | ||||
|         barLength = 60  # Modify this to change the length of the progress bar | ||||
|         status = "" | ||||
|         if isinstance(progress, int): | ||||
|             progress = float(progress) | ||||
|         if not isinstance(progress, float): | ||||
|             progress = 0 | ||||
|             status = "error: progress var must be float\r\n" | ||||
|         if progress < 0: | ||||
|             progress = 0 | ||||
|             status = "Halt...\r\n" | ||||
|         if progress >= 1: | ||||
|             progress = 1 | ||||
|             status = "Done...\r\n" | ||||
|         block = int(round(barLength * progress)) | ||||
|         text = "\rUploading: [{0}] {1}% {2}".format("=" * block + " " * (barLength - block), | ||||
|                                                     int(progress * 100), status) | ||||
|         sys.stderr.write(text) | ||||
|         sys.stderr.flush() | ||||
|     else: | ||||
|         sys.stderr.write('.') | ||||
|         sys.stderr.flush() | ||||
|  | ||||
|  | ||||
| def serve(remote_host, local_addr, remote_port, local_port, password, filename, command=FLASH): | ||||
|     # Create a TCP/IP socket | ||||
|     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
|     server_address = (local_addr, local_port) | ||||
|     _LOGGER.info('Starting on %s:%s', server_address[0], server_address[1]) | ||||
|     try: | ||||
|         sock.bind(server_address) | ||||
|         sock.listen(1) | ||||
|     except Exception: | ||||
|         _LOGGER.error("Listen Failed") | ||||
|         return 1 | ||||
|  | ||||
|     content_size = os.path.getsize(filename) | ||||
|     f = open(filename, 'rb') | ||||
|     file_md5 = hashlib.md5(f.read()).hexdigest() | ||||
|     f.close() | ||||
|     _LOGGER.info('Upload size: %d', content_size) | ||||
|     message = '%d %d %d %s\n' % (command, local_port, content_size, file_md5) | ||||
|  | ||||
|     # Wait for a connection | ||||
|     inv_trys = 0 | ||||
|     data = '' | ||||
|     msg = 'Sending invitation to {} '.format(remote_host) | ||||
|     _LOGGER.info(msg) | ||||
|     remote_address = (remote_host, int(remote_port)) | ||||
|     sock2 = None | ||||
|     while inv_trys < 10: | ||||
|         inv_trys += 1 | ||||
|         sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||||
|         try: | ||||
|             sock2.sendto(message.encode(), remote_address) | ||||
|         except Exception: | ||||
|             _LOGGER.error('Failed') | ||||
|             sock2.close() | ||||
|             _LOGGER.error('Host %s Not Found', remote_host) | ||||
|             return 1 | ||||
|         sock2.settimeout(1) | ||||
|         try: | ||||
|             data = sock2.recv(37).decode() | ||||
|             break | ||||
|         except Exception: | ||||
|             sys.stderr.write('.') | ||||
|             sys.stderr.flush() | ||||
|             sock2.close() | ||||
|     sys.stderr.write('\n') | ||||
|     sys.stderr.flush() | ||||
|     if inv_trys == 10: | ||||
|         _LOGGER.error('No response from the ESP') | ||||
|         return 1 | ||||
|     if data != "OK": | ||||
|         if data.startswith('AUTH'): | ||||
|             nonce = data.split()[1] | ||||
|             cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remote_host) | ||||
|             cnonce = hashlib.md5(cnonce_text.encode()).hexdigest() | ||||
|             passmd5 = hashlib.md5(password.encode()).hexdigest() | ||||
|             result_text = '%s:%s:%s' % (passmd5, nonce, cnonce) | ||||
|             result = hashlib.md5(result_text.encode()).hexdigest() | ||||
|             _LOGGER.info("Authenticating...") | ||||
|             message = '%d %s %s\n' % (AUTH, cnonce, result) | ||||
|             sock2.sendto(message.encode(), remote_address) | ||||
|             sock2.settimeout(10) | ||||
|             try: | ||||
|                 data = sock2.recv(32).decode() | ||||
|             except Exception: | ||||
|                 _LOGGER.error('FAIL: No Answer to our Authentication') | ||||
|                 sock2.close() | ||||
|                 return 1 | ||||
|             if data != "OK": | ||||
|                 _LOGGER.error('FAIL: %s', data) | ||||
|                 sock2.close() | ||||
|                 return 1 | ||||
|             _LOGGER.info('OK') | ||||
|         else: | ||||
|             _LOGGER.error('Bad Answer: %s', data) | ||||
|             sock2.close() | ||||
|             return 1 | ||||
|     sock2.close() | ||||
|  | ||||
|     _LOGGER.info('Waiting for device...') | ||||
|     try: | ||||
|         sock.settimeout(10) | ||||
|         connection, client_address = sock.accept() | ||||
|         sock.settimeout(None) | ||||
|         connection.settimeout(None) | ||||
|     except Exception: | ||||
|         _LOGGER.error('No response from device') | ||||
|         sock.close() | ||||
|         return 1 | ||||
|  | ||||
|     try: | ||||
|         f = open(filename, "rb") | ||||
|         if PROGRESS: | ||||
|             update_progress(0) | ||||
|         else: | ||||
|             _LOGGER.info('Uploading...') | ||||
|         offset = 0 | ||||
|         while True: | ||||
|             chunk = f.read(1024) | ||||
|             if not chunk: break | ||||
|             offset += len(chunk) | ||||
|             update_progress(offset / float(content_size)) | ||||
|             connection.settimeout(10) | ||||
|             try: | ||||
|                 connection.sendall(chunk) | ||||
|                 connection.recv(10) | ||||
|             except Exception: | ||||
|                 sys.stderr.write('\n') | ||||
|                 _LOGGER.error('Error Uploading') | ||||
|                 connection.close() | ||||
|                 f.close() | ||||
|                 sock.close() | ||||
|                 return 1 | ||||
|  | ||||
|         sys.stderr.write('\n') | ||||
|         _LOGGER.info('Waiting for result...') | ||||
|         try: | ||||
|             connection.settimeout(60) | ||||
|             while True: | ||||
|                 if connection.recv(32).decode().find('O') >= 0: | ||||
|                     break | ||||
|             _LOGGER.info('Result: OK') | ||||
|             connection.close() | ||||
|             f.close() | ||||
|             sock.close() | ||||
|             if data != "OK": | ||||
|                 _LOGGER.error('%s', data) | ||||
|                 return 1 | ||||
|         except Exception: | ||||
|             _LOGGER.error('No Result!') | ||||
|             connection.close() | ||||
|             f.close() | ||||
|             sock.close() | ||||
|             return 1 | ||||
|  | ||||
|     finally: | ||||
|         connection.close() | ||||
|         f.close() | ||||
|  | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def parser(unparsed_args): | ||||
|     parser = optparse.OptionParser( | ||||
|         usage="%prog [options]", | ||||
|         description="Transmit image over the air to the esp8266 module with OTA support." | ||||
|     ) | ||||
|  | ||||
|     # destination ip and port | ||||
|     group = optparse.OptionGroup(parser, "Destination") | ||||
|     group.add_option("-i", "--ip", | ||||
|                      dest="esp_ip", | ||||
|                      action="store", | ||||
|                      help="ESP8266 IP Address.", | ||||
|                      default=False | ||||
|                      ) | ||||
|     group.add_option("-I", "--host_ip", | ||||
|                      dest="host_ip", | ||||
|                      action="store", | ||||
|                      help="Host IP Address.", | ||||
|                      default="0.0.0.0" | ||||
|                      ) | ||||
|     group.add_option("-p", "--port", | ||||
|                      dest="esp_port", | ||||
|                      type="int", | ||||
|                      help="ESP8266 ota Port. Default 8266", | ||||
|                      default=8266 | ||||
|                      ) | ||||
|     group.add_option("-P", "--host_port", | ||||
|                      dest="host_port", | ||||
|                      type="int", | ||||
|                      help="Host server ota Port. Default random 10000-60000", | ||||
|                      default=random.randint(10000, 60000) | ||||
|                      ) | ||||
|     parser.add_option_group(group) | ||||
|  | ||||
|     # auth | ||||
|     group = optparse.OptionGroup(parser, "Authentication") | ||||
|     group.add_option("-a", "--auth", | ||||
|                      dest="auth", | ||||
|                      help="Set authentication password.", | ||||
|                      action="store", | ||||
|                      default="" | ||||
|                      ) | ||||
|     parser.add_option_group(group) | ||||
|  | ||||
|     # image | ||||
|     group = optparse.OptionGroup(parser, "Image") | ||||
|     group.add_option("-f", "--file", | ||||
|                      dest="image", | ||||
|                      help="Image file.", | ||||
|                      metavar="FILE", | ||||
|                      default=None | ||||
|                      ) | ||||
|     group.add_option("-s", "--spiffs", | ||||
|                      dest="spiffs", | ||||
|                      action="store_true", | ||||
|                      help="Use this option to transmit a SPIFFS image and do not flash the " | ||||
|                           "module.", | ||||
|                      default=False | ||||
|                      ) | ||||
|     parser.add_option_group(group) | ||||
|  | ||||
|     # output group | ||||
|     group = optparse.OptionGroup(parser, "Output") | ||||
|     group.add_option("-d", "--debug", | ||||
|                      dest="debug", | ||||
|                      help="Show debug output. And override loglevel with debug.", | ||||
|                      action="store_true", | ||||
|                      default=False | ||||
|                      ) | ||||
|     group.add_option("-r", "--progress", | ||||
|                      dest="progress", | ||||
|                      help="Show progress output. Does not work for ArduinoIDE", | ||||
|                      action="store_true", | ||||
|                      default=False | ||||
|                      ) | ||||
|     parser.add_option_group(group) | ||||
|  | ||||
|     (options, args) = parser.parse_args(unparsed_args) | ||||
|  | ||||
|     return options | ||||
|  | ||||
|  | ||||
| def main(args): | ||||
|     options = parser(args) | ||||
|     _LOGGER.debug("Options: %s", str(options)) | ||||
|  | ||||
|     # check options | ||||
|     global PROGRESS | ||||
|     PROGRESS = options.progress | ||||
|     if not options.esp_ip or not options.image: | ||||
|         _LOGGER.critical("Not enough arguments.") | ||||
|         return 1 | ||||
|  | ||||
|     command = FLASH | ||||
|     if options.spiffs: | ||||
|         command = SPIFFS | ||||
|  | ||||
|     return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, | ||||
|                  options.auth, options.image, command) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     sys.exit(main(sys.argv)) | ||||
							
								
								
									
										400
									
								
								esphomeyaml/helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								esphomeyaml/helpers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import logging | ||||
| import re | ||||
| from collections import OrderedDict, deque | ||||
|  | ||||
| from esphomeyaml.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, \ | ||||
|     CONF_INVERTED, \ | ||||
|     CONF_MODE, CONF_NUMBER, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, \ | ||||
|     CONF_STATE_TOPIC, CONF_TOPIC | ||||
| from esphomeyaml.core import ESPHomeYAMLError, HexInt | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| SIMPLIFY = False | ||||
|  | ||||
|  | ||||
| def ensure_unique_string(preferred_string, current_strings): | ||||
|     test_string = preferred_string | ||||
|     current_strings_set = set(current_strings) | ||||
|  | ||||
|     tries = 1 | ||||
|  | ||||
|     while test_string in current_strings_set: | ||||
|         tries += 1 | ||||
|         test_string = u"{}_{}".format(preferred_string, tries) | ||||
|  | ||||
|     return test_string | ||||
|  | ||||
|  | ||||
| def indent_all_but_first_and_last(text, padding=u'  '): | ||||
|     lines = text.splitlines(True) | ||||
|     if len(lines) <= 2: | ||||
|         return text | ||||
|     return lines[0] + u''.join(padding + line for line in lines[1:-1]) + lines[-1] | ||||
|  | ||||
|  | ||||
| def indent_list(text, padding=u'  '): | ||||
|     return [padding + line for line in text.splitlines()] | ||||
|  | ||||
|  | ||||
| def indent(text, padding=u'  '): | ||||
|     return u'\n'.join(indent_list(text, padding)) | ||||
|  | ||||
|  | ||||
| class Expression(object): | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
|     def __str__(self): | ||||
|         raise NotImplemented | ||||
|  | ||||
|  | ||||
| class RawExpression(Expression): | ||||
|     def __init__(self, text): | ||||
|         super(RawExpression, self).__init__() | ||||
|         self.text = text | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.text | ||||
|  | ||||
|  | ||||
| class AssignmentExpression(Expression): | ||||
|     def __init__(self, lhs, rhs, obj): | ||||
|         super(AssignmentExpression, self).__init__() | ||||
|         self.obj = obj | ||||
|         self.lhs = safe_exp(lhs) | ||||
|         self.rhs = safe_exp(rhs) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{} = {}".format(self.lhs, self.rhs) | ||||
|  | ||||
|  | ||||
| class ExpressionList(Expression): | ||||
|     def __init__(self, *args): | ||||
|         super(ExpressionList, self).__init__() | ||||
|         # Remove every None on end | ||||
|         args = list(args) | ||||
|         while args and args[-1] is None: | ||||
|             args.pop() | ||||
|         self.args = [safe_exp(x) for x in args] | ||||
|  | ||||
|     def __str__(self): | ||||
|         text = u", ".join(unicode(x) for x in self.args) | ||||
|         return indent_all_but_first_and_last(text) | ||||
|  | ||||
|  | ||||
| class CallExpression(Expression): | ||||
|     def __init__(self, base, *args): | ||||
|         super(CallExpression, self).__init__() | ||||
|         self.base = base | ||||
|         self.args = ExpressionList(*args) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u'{}({})'.format(self.base, self.args) | ||||
|  | ||||
|  | ||||
| class StructInitializer(Expression): | ||||
|     def __init__(self, base, *args): | ||||
|         super(StructInitializer, self).__init__() | ||||
|         self.base = base | ||||
|         if not isinstance(args, OrderedDict): | ||||
|             args = OrderedDict(args) | ||||
|         self.args = OrderedDict() | ||||
|         for key, value in args.iteritems(): | ||||
|             if value is not None: | ||||
|                 self.args[key] = safe_exp(value) | ||||
|  | ||||
|     def __str__(self): | ||||
|         s = u'{}{{\n'.format(self.base) | ||||
|         for key, value in self.args.iteritems(): | ||||
|             s += u'  .{} = {},\n'.format(key, value) | ||||
|         s += u'}' | ||||
|         return s | ||||
|  | ||||
|  | ||||
| class ArrayInitializer(Expression): | ||||
|     def __init__(self, *args): | ||||
|         super(ArrayInitializer, self).__init__() | ||||
|         self.args = [safe_exp(x) for x in args if x is not None] | ||||
|  | ||||
|     def __str__(self): | ||||
|         if not self.args: | ||||
|             return u'{}' | ||||
|         s = u'{\n' | ||||
|         for arg in self.args: | ||||
|             s += u'  {},\n'.format(arg) | ||||
|         s += u'}' | ||||
|         return s | ||||
|  | ||||
|  | ||||
| class Literal(Expression): | ||||
|     def __init__(self): | ||||
|         super(Literal, self).__init__() | ||||
|  | ||||
|  | ||||
| class StringLiteral(Literal): | ||||
|     def __init__(self, s): | ||||
|         super(StringLiteral, self).__init__() | ||||
|         self.s = s | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u'"{}"'.format(self.s) | ||||
|  | ||||
|  | ||||
| class IntLiteral(Literal): | ||||
|     def __init__(self, i): | ||||
|         super(IntLiteral, self).__init__() | ||||
|         self.i = i | ||||
|  | ||||
|     def __str__(self): | ||||
|         return unicode(self.i) | ||||
|  | ||||
|  | ||||
| class BoolLiteral(Literal): | ||||
|     def __init__(self, b): | ||||
|         super(BoolLiteral, self).__init__() | ||||
|         self.b = b | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"true" if self.b else u"false" | ||||
|  | ||||
|  | ||||
| class HexIntLiteral(Literal): | ||||
|     def __init__(self, i): | ||||
|         super(HexIntLiteral, self).__init__() | ||||
|         self.i = HexInt(i) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return str(self.i) | ||||
|  | ||||
|  | ||||
| class FloatLiteral(Literal): | ||||
|     def __init__(self, f): | ||||
|         super(FloatLiteral, self).__init__() | ||||
|         self.f = f | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{:f}f".format(self.f) | ||||
|  | ||||
|  | ||||
| def safe_exp(obj): | ||||
|     if isinstance(obj, Expression): | ||||
|         return obj | ||||
|     elif isinstance(obj, bool): | ||||
|         return BoolLiteral(obj) | ||||
|     elif isinstance(obj, str) or isinstance(obj, unicode): | ||||
|         return StringLiteral(obj) | ||||
|     elif isinstance(obj, (int, long)): | ||||
|         return IntLiteral(obj) | ||||
|     elif isinstance(obj, float): | ||||
|         return FloatLiteral(obj) | ||||
|     raise ValueError(u"Object is not an expression", obj) | ||||
|  | ||||
|  | ||||
| class Statement(object): | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
|     def __str__(self): | ||||
|         raise NotImplemented | ||||
|  | ||||
|  | ||||
| class RawStatement(Statement): | ||||
|     def __init__(self, text): | ||||
|         super(RawStatement, self).__init__() | ||||
|         self.text = text | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.text | ||||
|  | ||||
|  | ||||
| class ExpressionStatement(Statement): | ||||
|     def __init__(self, expression): | ||||
|         super(ExpressionStatement, self).__init__() | ||||
|         self.expression = safe_exp(expression) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return u"{};".format(self.expression) | ||||
|  | ||||
|  | ||||
| def statement(expression): | ||||
|     if isinstance(expression, Statement): | ||||
|         return expression | ||||
|     return ExpressionStatement(expression) | ||||
|  | ||||
|  | ||||
| def variable(type, id, rhs): | ||||
|     lhs = RawExpression(u'{} {}'.format(type if not SIMPLIFY else u'auto', id)) | ||||
|     rhs = safe_exp(rhs) | ||||
|     obj = MockObj(id, u'.') | ||||
|     add(AssignmentExpression(lhs, rhs, obj)) | ||||
|     _VARIABLES[id] = obj, type | ||||
|     return obj | ||||
|  | ||||
|  | ||||
| def Pvariable(type, id, rhs): | ||||
|     lhs = RawExpression(u'{} *{}'.format(type if not SIMPLIFY else u'auto', id)) | ||||
|     rhs = safe_exp(rhs) | ||||
|     obj = MockObj(id, u'->') | ||||
|     add(AssignmentExpression(lhs, rhs, obj)) | ||||
|     _VARIABLES[id] = obj, type | ||||
|     return obj | ||||
|  | ||||
|  | ||||
| _QUEUE = deque() | ||||
| _VARIABLES = {} | ||||
| _EXPRESSIONS = [] | ||||
|  | ||||
|  | ||||
| def get_variable(id, type=None): | ||||
|     result = None | ||||
|     while _QUEUE: | ||||
|         if id is not None: | ||||
|             if id in _VARIABLES: | ||||
|                 result = _VARIABLES[id][0] | ||||
|                 break | ||||
|         elif type is not None: | ||||
|             result = next((x[0] for x in _VARIABLES.itervalues() if x[1] == type), None) | ||||
|             if result is not None: | ||||
|                 break | ||||
|         func, config = _QUEUE.popleft() | ||||
|         func(config) | ||||
|     if id is None and type is None: | ||||
|         return None | ||||
|     if result is None: | ||||
|         if id is not None: | ||||
|             result = _VARIABLES[id][0] | ||||
|         elif type is not None: | ||||
|             result = next((x[0] for x in _VARIABLES.itervalues() if x[1] == type), None) | ||||
|  | ||||
|         if result is None: | ||||
|             raise ESPHomeYAMLError(u"Couldn't find ID '{}' with type {}".format(id, type)) | ||||
|     result.usages += 1 | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def add_task(func, config): | ||||
|     _QUEUE.append((func, config)) | ||||
|  | ||||
|  | ||||
| def add(expression): | ||||
|     _EXPRESSIONS.append(expression) | ||||
|     return expression | ||||
|  | ||||
|  | ||||
| class MockObj(Expression): | ||||
|     def __init__(self, base, op=u'.', parent=None): | ||||
|         self.base = base | ||||
|         self.op = op | ||||
|         self.usages = 0 | ||||
|         self.parent = parent | ||||
|         super(MockObj, self).__init__() | ||||
|  | ||||
|     def __getattr__(self, attr): | ||||
|         next_op = u'.' | ||||
|         if attr.startswith(u'P'): | ||||
|             attr = attr[1:] | ||||
|             next_op = u'->' | ||||
|         op = self.op | ||||
|         return MockObj(u'{}{}{}'.format(self.base, op, attr), next_op, self) | ||||
|  | ||||
|     def __call__(self, *args, **kwargs): | ||||
|         self.usages += 1 | ||||
|         it = self.parent | ||||
|         while it is not None: | ||||
|             it.usages += 1 | ||||
|             it = it.parent | ||||
|         return CallExpression(self.base, *args) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.base | ||||
|  | ||||
|  | ||||
| App = MockObj(u'App') | ||||
|  | ||||
| GPIOPin = MockObj(u'GPIOPin') | ||||
| GPIOOutputPin = MockObj(u'GPIOOutputPin') | ||||
| GPIOInputPin = MockObj(u'GPIOInputPin') | ||||
|  | ||||
|  | ||||
| def get_gpio_pin_number(conf): | ||||
|     if isinstance(conf, int): | ||||
|         return conf | ||||
|     return conf[CONF_NUMBER] | ||||
|  | ||||
|  | ||||
| def exp_gpio_pin_(obj, conf, default_mode): | ||||
|     if isinstance(conf, int): | ||||
|         return conf | ||||
|     if conf.get(CONF_INVERTED) is None: | ||||
|         return obj(conf[CONF_NUMBER], conf.get(CONF_MODE)) | ||||
|     return obj(conf[CONF_NUMBER], RawExpression(conf.get(CONF_MODE, default_mode)), | ||||
|                conf[CONF_INVERTED]) | ||||
|  | ||||
|  | ||||
| def exp_gpio_pin(conf): | ||||
|     return GPIOPin(conf[CONF_NUMBER], conf[CONF_MODE], conf.get(CONF_INVERTED)) | ||||
|  | ||||
|  | ||||
| def exp_gpio_output_pin(conf): | ||||
|     return exp_gpio_pin_(GPIOOutputPin, conf, u'OUTPUT') | ||||
|  | ||||
|  | ||||
| def exp_gpio_input_pin(conf): | ||||
|     return exp_gpio_pin_(GPIOInputPin, conf, u'INPUT') | ||||
|  | ||||
|  | ||||
| def setup_mqtt_component(obj, config): | ||||
|     if CONF_RETAIN in config: | ||||
|         add(obj.set_retain(config[CONF_RETAIN])) | ||||
|     if not config.get(CONF_DISCOVERY, True): | ||||
|         add(obj.disable_discovery()) | ||||
|     if CONF_STATE_TOPIC in config: | ||||
|         add(obj.set_custom_state_topic(config[CONF_STATE_TOPIC])) | ||||
|     if CONF_COMMAND_TOPIC in config: | ||||
|         add(obj.set_custom_command_topic(config[CONF_COMMAND_TOPIC])) | ||||
|     if CONF_AVAILABILITY in config: | ||||
|         availability = config[CONF_AVAILABILITY] | ||||
|         exp = StructInitializer( | ||||
|             u'mqtt::Availability', | ||||
|             (u'topic', availability[CONF_TOPIC]), | ||||
|             (u'payload_available', availability[CONF_PAYLOAD_AVAILABLE]), | ||||
|             (u'payload_not_available', availability[CONF_PAYLOAD_NOT_AVAILABLE]), | ||||
|         ) | ||||
|         add(obj.set_availability(exp)) | ||||
|  | ||||
|  | ||||
| def exp_empty_optional(type): | ||||
|     return RawExpression(u'Optional<{}>()'.format(type)) | ||||
|  | ||||
|  | ||||
| def exp_optional(type, value): | ||||
|     if value is None: | ||||
|         return exp_empty_optional(type) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| # shlex's quote for Python 2.7 | ||||
| _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search | ||||
|  | ||||
|  | ||||
| def quote(s): | ||||
|     """Return a shell-escaped version of the string *s*.""" | ||||
|     if not s: | ||||
|         return u"''" | ||||
|     if _find_unsafe(s) is None: | ||||
|         return s | ||||
|  | ||||
|     # use single quotes, and put single quotes into double quotes | ||||
|     # the string $'b is then quoted as '$'"'"'b' | ||||
|     return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" | ||||
|  | ||||
|  | ||||
| def color(the_color, message = '', reset=None): | ||||
|     """Color helper.""" | ||||
|     from colorlog.escape_codes import escape_codes, parse_colors | ||||
|     if not message: | ||||
|         return parse_colors(the_color) | ||||
|     return parse_colors(the_color) + message + escape_codes[reset or 'reset'] | ||||
							
								
								
									
										69
									
								
								esphomeyaml/mqtt.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphomeyaml/mqtt.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import logging | ||||
| from datetime import datetime | ||||
|  | ||||
| import paho.mqtt.client as mqtt | ||||
|  | ||||
| from esphomeyaml.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOMEYAML, CONF_LOGGER, \ | ||||
|     CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TOPIC_PREFIX, \ | ||||
|     CONF_USERNAME | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def initialize(config, subscriptions, on_message, username, password, client_id): | ||||
|     def on_connect(client, userdata, flags, rc): | ||||
|         for topic in subscriptions: | ||||
|             client.subscribe(topic) | ||||
|  | ||||
|     client = mqtt.Client(client_id or u'') | ||||
|     client.on_connect = on_connect | ||||
|     client.on_message = on_message | ||||
|     if username is None: | ||||
|         if config[CONF_MQTT].get(CONF_USERNAME): | ||||
|             client.username_pw_set(config[CONF_MQTT][CONF_USERNAME], | ||||
|                                    config[CONF_MQTT][CONF_PASSWORD]) | ||||
|     elif username: | ||||
|         client.username_pw_set(username, password) | ||||
|     client.connect(config[CONF_MQTT][CONF_BROKER], config[CONF_MQTT][CONF_PORT]) | ||||
|  | ||||
|     try: | ||||
|         client.loop_forever() | ||||
|     except KeyboardInterrupt: | ||||
|         pass | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def show_logs(config, topic=None, username=None, password=None, client_id=None): | ||||
|     if topic is not None: | ||||
|         pass  # already have topic | ||||
|     elif CONF_LOG_TOPIC in config.get(CONF_LOGGER, {}): | ||||
|         topic = config[CONF_LOGGER][CONF_LOG_TOPIC] | ||||
|     elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: | ||||
|         topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + u'/debug' | ||||
|     else: | ||||
|         topic = config[CONF_ESPHOMEYAML][CONF_NAME] + u'/debug' | ||||
|     _LOGGER.info(u"Starting log output from %s", topic) | ||||
|  | ||||
|     def on_message(client, userdata, msg): | ||||
|         t = datetime.now().time().strftime(u'[%H:%M:%S] ') | ||||
|         print(t + msg.payload) | ||||
|  | ||||
|     return initialize(config, [topic], on_message, username, password, client_id) | ||||
|  | ||||
|  | ||||
| def clear_topic(config, topic, username=None, password=None, client_id=None): | ||||
|     if topic is None: | ||||
|         discovery_prefix = config[CONF_MQTT].get(CONF_DISCOVERY_PREFIX, u'homeassistant') | ||||
|         name = config[CONF_ESPHOMEYAML][CONF_NAME] | ||||
|         topic = u'{}/+/{}/#'.format(discovery_prefix, name) | ||||
|     _LOGGER.info(u"Clearing messages from {}".format(topic)) | ||||
|  | ||||
|     def on_message(client, userdata, msg): | ||||
|         if not msg.payload: | ||||
|             return | ||||
|         print(u"Clearing topic {}".format(msg.topic)) | ||||
|         client.publish(msg.topic, None, retain=True) | ||||
|  | ||||
|     return initialize(config, [topic], on_message, username, password, client_id) | ||||
							
								
								
									
										196
									
								
								esphomeyaml/pins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								esphomeyaml/pins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| import logging | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.const import ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266, CONF_NUMBER, CONF_MODE, \ | ||||
|     CONF_INVERTED | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| ESP8266_PINS = { | ||||
|     'A0': 17, 'SS': 15, 'MOSI': 13, 'MISO': 12, 'SCK': 14, | ||||
| } | ||||
| ESP8266_NODEMCU_PINS = dict(ESP8266_PINS, **{ | ||||
|     'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 15, 'D9': 3, | ||||
|     'D10': 1, 'LED': 16, 'SDA': 4, 'SCL': 5, | ||||
| }) | ||||
| ESP8266_D1_PINS = dict(ESP8266_PINS, **{ | ||||
|     'D0': 3, 'D1': 1, 'D2': 16, 'D3': 5, 'D4': 4, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 0, 'D9': 2, | ||||
|     'D10': 15, 'D11': 13, 'D12': 14, 'D13': 14, 'D14': 4, 'D15': 5, 'LED': 2, 'SDA': 4, 'SCL': 5, | ||||
| }) | ||||
| ESP8266_D1_MINI_PINS = dict(ESP8266_PINS, **{ | ||||
|     'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 0, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 15, 'RX': 3, | ||||
|     'TX': 1, 'LED': 2, 'SDA': 4, 'SCL': 5, | ||||
| }) | ||||
| ESP8266_THING_PINS = dict(ESP8266_PINS, **{ | ||||
|     'LED': 5, 'SDA': 2, 'SCL': 14, | ||||
| }) | ||||
| ESP8266_ADAFRUIT_PINS = dict(ESP8266_PINS, **{ | ||||
|     'LED': 0, 'SDA': 4, 'SCL': 5, | ||||
| }) | ||||
| ESP8266_ESPDUINO_PINS = dict(ESP8266_PINS, **{ | ||||
|     'LED': 16, 'SDA': 4, 'SCL': 5, | ||||
| }) | ||||
| ESP8266_BOARD_TO_PINS = { | ||||
|     'huzzah': ESP8266_ADAFRUIT_PINS, | ||||
|     'espduino': ESP8266_ESPDUINO_PINS, | ||||
|     'nodemcu': ESP8266_NODEMCU_PINS, 'nodemcuv2': ESP8266_NODEMCU_PINS, | ||||
|     'thing': ESP8266_THING_PINS, 'thingdev': ESP8266_THING_PINS, | ||||
|     'd1': ESP8266_D1_PINS, | ||||
|     'd1_mini': ESP8266_D1_MINI_PINS, 'd1_mini_lite': ESP8266_D1_MINI_PINS, | ||||
|     'd1_mini_pro': ESP8266_D1_MINI_PINS | ||||
| } | ||||
|  | ||||
| ESP32_PINS = { | ||||
|     'TX': 1, 'RX': 3, 'SDA': 21, 'SCL': 22, 'SS': 5, 'MOSI': 23, 'MISO': 19, 'SCK': 18, 'A0': 36, | ||||
|     'A3': 39, 'A4': 32, 'A5': 33, 'A6': 34, 'A7': 35, 'A10': 4, 'A11': 0, 'A12': 2, 'A13': 15, | ||||
|     'A14': 13, 'A15': 12, 'A16': 14, 'A17': 27, 'A18': 25, 'A19': 26, 'T0': 4, 'T1': 0, 'T2': 2, | ||||
|     'T3': 15, 'T4': 12, 'T5': 12, 'T6': 14, 'T7': 27, 'T8': 33, 'T9': 32, 'DAC1': 25, 'DAC2': 26, | ||||
|     'SVP': 36, 'SVN': 39, | ||||
| } | ||||
| ESP32_NODEMCU_32S_PINS = dict(ESP32_PINS, **{ | ||||
|     'LED': 2, | ||||
| }) | ||||
| ESP32_LOLIN32_PINS = dict(ESP32_PINS, **{ | ||||
|     'LED': 5 | ||||
| }) | ||||
| ESP32_BOARD_TO_PINS = { | ||||
|     'nodemcu-32s': ESP32_NODEMCU_32S_PINS, | ||||
|     'lolin32': ESP32_LOLIN32_PINS, | ||||
| } | ||||
|  | ||||
|  | ||||
| def _translate_pin(value): | ||||
|     if isinstance(value, dict) or value is None: | ||||
|         raise vol.Invalid(u"This option doesn't allow more complicated options like inverted.") | ||||
|     if isinstance(value, int): | ||||
|         return value | ||||
|     try: | ||||
|         return int(value) | ||||
|     except ValueError: | ||||
|         pass | ||||
|     if value.startswith('GPIO'): | ||||
|         return vol.Coerce(int)(value[len('GPIO'):]) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         if value in ESP32_PINS: | ||||
|             return ESP32_PINS[value] | ||||
|         if cv.BOARD not in ESP32_BOARD_TO_PINS: | ||||
|             raise vol.Invalid(u"ESP32: Unknown board {} with unknown " | ||||
|                               u"pin {}.".format(cv.BOARD, value)) | ||||
|         if value not in ESP32_BOARD_TO_PINS[cv.BOARD]: | ||||
|             raise vol.Invalid(u"ESP32: Board {} doesn't have" | ||||
|                               u"pin {}".format(cv.BOARD, value)) | ||||
|         return ESP32_BOARD_TO_PINS[cv.BOARD][value] | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         if value in ESP8266_PINS: | ||||
|             return ESP8266_PINS[value] | ||||
|         if cv.BOARD not in ESP8266_BOARD_TO_PINS: | ||||
|             raise vol.Invalid(u"ESP8266: Unknown board {} with unknown " | ||||
|                               u"pin {}.".format(cv.BOARD, value)) | ||||
|         if value not in ESP8266_BOARD_TO_PINS[cv.BOARD]: | ||||
|             raise vol.Invalid(u"ESP8266: Board {} doesn't have" | ||||
|                               u"pin {}".format(cv.BOARD, value)) | ||||
|         return ESP8266_BOARD_TO_PINS[cv.BOARD][value] | ||||
|     raise vol.Invalid(u"Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| def _validate_gpio_pin(value): | ||||
|     value = _translate_pin(value) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         if value < 0 or value > 39: | ||||
|             raise vol.Invalid(u"ESP32: Invalid pin number: {}".format(value)) | ||||
|         if 6 <= value <= 11: | ||||
|             _LOGGER.warning(u"ESP32: Pin {} (6-11) might already be used by the " | ||||
|                             u"flash interface. Be warned.".format(value)) | ||||
|         if value in (20, 24, 28, 29, 30, 31): | ||||
|             _LOGGER.warning(u"ESP32: Pin {} (20, 24, 28-31) can usually not be used. " | ||||
|                             u"Be warned.".format(value)) | ||||
|         return value | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         if 6 <= value <= 11: | ||||
|             _LOGGER.warning(u"ESP8266: Pin {} (6-11) might already be used by the " | ||||
|                             u"flash interface. Be warned.".format(value)) | ||||
|         if value < 0 or value > 17: | ||||
|             raise vol.Invalid(u"ESP8266: Invalid pin number: {}".format(value)) | ||||
|         return value | ||||
|     raise vol.Invalid(u"Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| def input_pin(value): | ||||
|     value = _validate_gpio_pin(value) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         return value | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         return value | ||||
|     raise vol.Invalid(u"Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| def output_pin(value): | ||||
|     value = _validate_gpio_pin(value) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         if 34 <= value <= 39: | ||||
|             raise vol.Invalid(u"ESP32: Pin {} (34-39) can only be used as " | ||||
|                               u"input pins.".format(value)) | ||||
|         return value | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         if value == 16: | ||||
|             raise vol.Invalid(u"Pin {} doesn't support output mode".format(value)) | ||||
|         return value | ||||
|     raise vol.Invalid("Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| def analog_pin(value): | ||||
|     value = _validate_gpio_pin(value) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         if 32 <= value <= 39:  # ADC1 | ||||
|             return value | ||||
|         raise vol.Invalid(u"ESP32: Only pins 32 though 39 support ADC.") | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         if value == 17:  # A0 | ||||
|             return value | ||||
|         raise vol.Invalid(u"ESP8266: Only pin A0 (17) supports ADC.") | ||||
|     raise vol.Invalid(u"Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| input_output_pin = vol.All(input_pin, output_pin) | ||||
| gpio_pin = vol.Any(input_pin, output_pin) | ||||
| PIN_MODES_ESP8266 = [ | ||||
|     'INPUT', 'OUTPUT', 'INPUT_PULLUP', 'OUTPUT_OPEN_DRAIN', 'SPECIAL', 'FUNCTION_1', | ||||
|     'FUNCTION_2', 'FUNCTION_3', 'FUNCTION_4', | ||||
|     'FUNCTION_0', 'WAKEUP_PULLUP', 'WAKEUP_PULLDOWN', 'INPUT_PULLDOWN_16', | ||||
| ] | ||||
| PIN_MODES_ESP32 = [ | ||||
|     'INPUT', 'OUTPUT', 'INPUT_PULLUP', 'OUTPUT_OPEN_DRAIN', 'SPECIAL', 'FUNCTION_1', | ||||
|     'FUNCTION_2', 'FUNCTION_3', 'FUNCTION_4', | ||||
|     'PULLUP', 'PULLDOWN', 'INPUT_PULLDOWN', 'OPEN_DRAIN', 'FUNCTION_5', | ||||
|     'FUNCTION_6', 'ANALOG', | ||||
| ] | ||||
|  | ||||
|  | ||||
| def pin_mode(value): | ||||
|     value = vol.All(vol.Coerce(str), vol.Upper)(value) | ||||
|     if cv.ESP_PLATFORM == ESP_PLATFORM_ESP32: | ||||
|         return vol.Any(*PIN_MODES_ESP32)(value) | ||||
|     elif cv.ESP_PLATFORM == ESP_PLATFORM_ESP8266: | ||||
|         return vol.Any(*PIN_MODES_ESP8266)(value) | ||||
|     raise vol.Invalid(u"Invalid ESP platform.") | ||||
|  | ||||
|  | ||||
| GPIO_PIN_SCHEMA = vol.Schema({ | ||||
|     vol.Required(CONF_NUMBER): gpio_pin, | ||||
|     vol.Required(CONF_MODE): pin_mode, | ||||
|     vol.Optional(CONF_INVERTED): cv.boolean, | ||||
| }) | ||||
|  | ||||
| GPIO_OUTPUT_PIN_SCHEMA = vol.Any(output_pin, vol.Schema({ | ||||
|     vol.Required(CONF_NUMBER): output_pin, | ||||
|     vol.Optional(CONF_MODE): pin_mode, | ||||
|     vol.Optional(CONF_INVERTED): cv.boolean, | ||||
| })) | ||||
|  | ||||
| GPIO_INPUT_PIN_SCHEMA = vol.Any(input_pin, vol.Schema({ | ||||
|     vol.Required(CONF_NUMBER): input_pin, | ||||
|     vol.Optional(CONF_MODE): pin_mode, | ||||
|     vol.Optional(CONF_INVERTED): cv.boolean, | ||||
| })) | ||||
							
								
								
									
										293
									
								
								esphomeyaml/wizard.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								esphomeyaml/wizard.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import codecs | ||||
| import os | ||||
| from time import sleep | ||||
| import unicodedata | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphomeyaml.config_validation as cv | ||||
| from esphomeyaml.components import mqtt | ||||
| from esphomeyaml.const import ESP_PLATFORMS, ESP_PLATFORM_ESP32, ESP_BOARDS_FOR_PLATFORM | ||||
| from esphomeyaml.helpers import color | ||||
|  | ||||
| CORE_BIG = """    _____ ____  _____  ______  | ||||
|    / ____/ __ \|  __ \|  ____| | ||||
|   | |   | |  | | |__) | |__    | ||||
|   | |   | |  | |  _  /|  __|   | ||||
|   | |___| |__| | | \ \| |____  | ||||
|    \_____\____/|_|  \_\______| | ||||
| """ | ||||
| ESP_BIG = """      ______  _____ _____   | ||||
|      |  ____|/ ____|  __ \  | ||||
|      | |__  | (___ | |__) | | ||||
|      |  __|  \___ \|  ___/  | ||||
|      | |____ ____) | |      | ||||
|      |______|_____/|_|      | ||||
| """ | ||||
| WIFI_BIG = """   __          ___ ______ _  | ||||
|    \ \        / (_)  ____(_) | ||||
|     \ \  /\  / / _| |__   _  | ||||
|      \ \/  \/ / | |  __| | | | ||||
|       \  /\  /  | | |    | | | ||||
|        \/  \/   |_|_|    |_| | ||||
| """ | ||||
| MQTT_BIG = """   __  __  ____ _______ _______  | ||||
|   |  \/  |/ __ \__   __|__   __| | ||||
|   | \  / | |  | | | |     | |    | ||||
|   | |\/| | |  | | | |     | |    | ||||
|   | |  | | |__| | | |     | |    | ||||
|   |_|  |_|\___\_\ |_|     |_|    | ||||
| """ | ||||
| OTA_BIG = """       ____ _______        | ||||
|       / __ \__   __|/\     | ||||
|      | |  | | | |  /  \    | ||||
|      | |  | | | | / /\ \   | ||||
|      | |__| | | |/ ____ \  | ||||
|       \____/  |_/_/    \_\\ | ||||
| """ | ||||
|  | ||||
| # TODO handle escaping | ||||
| BASE_CONFIG = """esphomeyaml: | ||||
|   name: {name} | ||||
|   platform: {platform} | ||||
|   board: {board} | ||||
|  | ||||
| wifi: | ||||
|   ssid: '{ssid}' | ||||
|   password: '{psk}' | ||||
|  | ||||
| mqtt: | ||||
|   broker: '{broker}' | ||||
|   username: '{mqtt_username}' | ||||
|   password: '{mqtt_password}' | ||||
|  | ||||
| # Enable logging | ||||
| logger: | ||||
|  | ||||
| """ | ||||
|  | ||||
|  | ||||
| def print_step(step, big): | ||||
|     print() | ||||
|     print() | ||||
|     print("============= STEP {} =============".format(step)) | ||||
|     print(big) | ||||
|     print("===================================") | ||||
|     sleep(0.25) | ||||
|  | ||||
|  | ||||
| def default_input(text, default): | ||||
|     print() | ||||
|     print("Press ENTER for default ({})".format(default)) | ||||
|     return raw_input(text.format(default)) or default | ||||
|  | ||||
|  | ||||
| # From https://stackoverflow.com/a/518232/8924614 | ||||
| def strip_accents(s): | ||||
|     return u''.join(c for c in unicodedata.normalize('NFD', unicode(s)) | ||||
|                     if unicodedata.category(c) != 'Mn') | ||||
|  | ||||
|  | ||||
| def wizard(path): | ||||
|     if not path.endswith('.yaml') and not path.endswith('.yml'): | ||||
|         print("Please make your configuration file {} have the extension .yaml or .yml".format( | ||||
|             color('cyan', path))) | ||||
|         return 1 | ||||
|     if os.path.exists(path): | ||||
|         print("Uh oh, it seems like {} already exists, please delete that file first " | ||||
|               "or chose another configuration file.".format(color('cyan', path))) | ||||
|         return 1 | ||||
|     print("Hi there!") | ||||
|     sleep(1.5) | ||||
|     print("I'm the wizard of esphomeyaml :)") | ||||
|     sleep(1.25) | ||||
|     print("And I'm here to help you get started with esphomeyaml.") | ||||
|     sleep(2.0) | ||||
|     print("In 5 steps I'm going to guide you through creating a basic " | ||||
|           "configuration file for your custom ESP8266/ESP32 firmware. Yay!") | ||||
|     sleep(3.0) | ||||
|     print() | ||||
|     print_step(1, CORE_BIG) | ||||
|     print("First up, please choose a " + color('green', 'name') + " for your node.") | ||||
|     print("It should be a unique name that can be used to identify the device later.") | ||||
|     sleep(1) | ||||
|     print("For example, I like calling the node in my living room {}.".format( | ||||
|         color('bold_white', "livingroom"))) | ||||
|     print() | ||||
|     sleep(1) | ||||
|     name = raw_input(color("bold_white", "(name): ")) | ||||
|     while True: | ||||
|         try: | ||||
|             name = cv.valid_name(name) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             print(color("red", "Oh noes, \"{}\" isn't a valid name. Names can only include " | ||||
|                                "numbers, letters and underscores.".format(name))) | ||||
|             name = strip_accents(name).replace(' ', '_') | ||||
|             name = u''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS) | ||||
|             print("Shall I use \"{}\" as the name instead?".format(color('cyan', name))) | ||||
|             sleep(0.5) | ||||
|             name = default_input("(name [{}]): ", name) | ||||
|  | ||||
|     print("Great! Your node is now called \"{}\".".format(color('cyan', name))) | ||||
|     sleep(1) | ||||
|     print_step(2, ESP_BIG) | ||||
|     print("Now I'd like to know which *board* you're using so that I can compile " | ||||
|           "firmwares for it.") | ||||
|     print("Are you using an " + color('green', 'ESP32') + " or " + | ||||
|           color('green', 'ESP8266') + " based board?") | ||||
|     while True: | ||||
|         sleep(0.5) | ||||
|         print() | ||||
|         print("Please enter either ESP32 or ESP8266.") | ||||
|         platform = raw_input(color("bold_white", "(ESP32/ESP8266): ")) | ||||
|         try: | ||||
|             platform = vol.All(vol.Upper, vol.Any(*ESP_PLATFORMS))(platform) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             print("Unfortunately, I can't find an espressif microcontroller called " | ||||
|                   "\"{}\". Please try again.".format(platform)) | ||||
|     print("Thanks! You've chosen {} as your platform.".format(color('cyan', platform))) | ||||
|     print() | ||||
|     sleep(1) | ||||
|  | ||||
|     if platform == ESP_PLATFORM_ESP32: | ||||
|         board_link = 'http://docs.platformio.org/en/latest/platforms/espressif32.html#boards' | ||||
|     else: | ||||
|         board_link = 'http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards' | ||||
|  | ||||
|     print("Next, I need to know what " + color('green', 'board') + " you're using.") | ||||
|     sleep(0.5) | ||||
|     print("Please go to {} and choose a board.".format(color('green', board_link))) | ||||
|     print() | ||||
|     # Don't sleep because user needs to copy link | ||||
|     if platform == ESP_PLATFORM_ESP32: | ||||
|         print("For example \"{}\".".format(color("bold_white", 'nodemcu-32s'))) | ||||
|     else: | ||||
|         print("For example \"{}\".".format(color("bold_white", 'nodemcuv2'))) | ||||
|     while True: | ||||
|         board = raw_input(color("bold_white", "(board): ")) | ||||
|         boards = ESP_BOARDS_FOR_PLATFORM[platform] | ||||
|         try: | ||||
|             board = vol.All(vol.Lower, vol.Any(*boards))(board) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             print(color('red', "Sorry, I don't think the board \"{}\" exists.")) | ||||
|             print() | ||||
|             sleep(0.25) | ||||
|             print("Possible options are {}".format(', '.join(boards))) | ||||
|             print() | ||||
|  | ||||
|     print("Way to go! You've chosen {} as your board.".format(color('cyan', board))) | ||||
|     print() | ||||
|     sleep(1) | ||||
|  | ||||
|     print_step(3, WIFI_BIG) | ||||
|     print("In this step, I'm going to create the configuration for " | ||||
|           "WiFi.") | ||||
|     print() | ||||
|     sleep(1) | ||||
|     print("First, what's the " + color('green', 'SSID') + " (the name) of the WiFi network {} " | ||||
|           "I should connect to?".format(name)) | ||||
|     sleep(1.5) | ||||
|     print("For example \"{}\".".format(color('bold_white', "Abraham Linksys"))) | ||||
|     while True: | ||||
|         ssid = raw_input(color('bold_white', "(ssid): ")) | ||||
|         try: | ||||
|             ssid = cv.ssid(ssid) | ||||
|             break | ||||
|         except vol.Invalid: | ||||
|             print(color('red', "Unfortunately, \"{}\" doesn't seem to be a valid SSID. " | ||||
|                   "Please try again.".format(ssid))) | ||||
|             print() | ||||
|             sleep(1) | ||||
|  | ||||
|     print("Thank you very much! You've just chosen \"{}\" as your SSID.".format( | ||||
|         color('cyan', ssid))) | ||||
|     print() | ||||
|     sleep(0.75) | ||||
|  | ||||
|     print("Now please state the " + color('green', 'password') + | ||||
|           " of the WiFi network so that I can connect to it.") | ||||
|     print() | ||||
|     print("For example \"{}\"".format(color('bold_white', 'PASSWORD42'))) | ||||
|     sleep(0.5) | ||||
|     psk = raw_input(color('bold_white', '(PSK): ')) | ||||
|     print("Perfect! WiFi is now set up (you can create static IPs and so on later).") | ||||
|     sleep(1.5) | ||||
|  | ||||
|     print_step(4, MQTT_BIG) | ||||
|     print("Almost there! Now let's setup MQTT so that your node can connect to the outside world.") | ||||
|     print() | ||||
|     sleep(1) | ||||
|     print("Please enter the " + color('green', 'address') + " of your MQTT broker.") | ||||
|     print() | ||||
|     print("For example \"{}\".".format(color('bold_white', '192.168.178.84'))) | ||||
|     while True: | ||||
|         broker = raw_input(color('bold_white', "(broker): ")) | ||||
|         try: | ||||
|             broker = mqtt.validate_broker(broker) | ||||
|             break | ||||
|         except vol.Invalid as e: | ||||
|             print(color('red', "The broker address \"{}\" seems to be invalid: {} :(".format( | ||||
|                 broker, e))) | ||||
|             print("Please try again.") | ||||
|             print() | ||||
|             sleep(1) | ||||
|  | ||||
|     print("Thanks! Now enter the " + color('green', 'username') + " and " + | ||||
|           color('green', 'password') + " for the MQTT broker. Leave empty for no authentication.") | ||||
|     mqtt_username = raw_input(color('bold_white', '(username): ')) | ||||
|     mqtt_password = '' | ||||
|     if mqtt_username: | ||||
|         mqtt_password = raw_input(color('bold_white', '(password): ')) | ||||
|  | ||||
|         show = '*' * len(mqtt_password) | ||||
|         if len(mqtt_password) >= 2: | ||||
|             show = mqtt_password[:2] + '*' * len(mqtt_password) | ||||
|         print("MQTT Username: \"{}\"; Password: \"{}\"".format( | ||||
|             color('cyan', mqtt_username), color('cyan', show))) | ||||
|     else: | ||||
|         print("No authentication for MQTT") | ||||
|  | ||||
|     print_step(5, OTA_BIG) | ||||
|     print("Last step! esphomeyaml can automatically upload custom firmwares over WiFi " | ||||
|           "(over the air).") | ||||
|     print("This can be insecure if you do not trust the WiFi network. Do you want to set " | ||||
|           "an " + color('green', 'OTA password') + " for remote updates?") | ||||
|     print() | ||||
|     sleep(0.25) | ||||
|     print("Press ENTER for no password") | ||||
|     ota_password = raw_input(color('bold_white', '(password): ')) | ||||
|  | ||||
|     config = BASE_CONFIG.format(name=name, platform=platform, board=board, | ||||
|                                 ssid=ssid, psk=psk, broker=broker, | ||||
|                                 mqtt_username=mqtt_username, mqtt_password=mqtt_password) | ||||
|  | ||||
|     if ota_password: | ||||
|         config += "ota:\n  password: '{}'".format(ota_password) | ||||
|     else: | ||||
|         config += "ota:\n" | ||||
|  | ||||
|     with codecs.open(path, 'w') as f: | ||||
|         f.write(config) | ||||
|  | ||||
|     print() | ||||
|     print(color('cyan', "DONE! I've now written a new configuration file to ") + | ||||
|           color('bold_cyan', path)) | ||||
|     print() | ||||
|     print("Next steps:") | ||||
|     print("  > If you haven't already, enable MQTT discovery in Home Assistant:") | ||||
|     print() | ||||
|     print(color('bold_white', "# In your configuration.yaml")) | ||||
|     print(color('bold_white', "mqtt:")) | ||||
|     print(color('bold_white', "  broker: {}".format(broker))) | ||||
|     print(color('bold_white', "  # ...")) | ||||
|     print(color('bold_white', "  discovery: True")) | ||||
|     print() | ||||
|     print("  > Then follow the rest of the getting started guide:") | ||||
|     print("  > https://esphomelib.com/esphomeyaml/getting-started.html") | ||||
|     return 0 | ||||
|  | ||||
							
								
								
									
										149
									
								
								esphomeyaml/writer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								esphomeyaml/writer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import codecs | ||||
| import errno | ||||
| import os | ||||
|  | ||||
| from esphomeyaml.config import get_component | ||||
| from esphomeyaml.const import CONF_BOARD, CONF_ESPHOMEYAML, CONF_LIBRARY_URI, CONF_LOGGER, \ | ||||
|     CONF_NAME, CONF_OTA, CONF_PLATFORM, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266 | ||||
| from esphomeyaml.core import ESPHomeYAMLError | ||||
|  | ||||
| CPP_AUTO_GENERATE_BEGIN = u'// ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| CPP_AUTO_GENERATE_END = u'// =========== AUTO GENERATED CODE END ============' | ||||
| INI_AUTO_GENERATE_BEGIN = u'; ========== AUTO GENERATED CODE BEGIN ===========' | ||||
| INI_AUTO_GENERATE_END = u'; =========== AUTO GENERATED CODE END ============' | ||||
|  | ||||
| CPP_BASE_FORMAT = (u"""// Auto generated code by esphomeyaml | ||||
| #include "esphomelib/application.h" | ||||
|  | ||||
| using namespace esphomelib; | ||||
|  | ||||
| void setup() { | ||||
|   // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== | ||||
|   """, u""" | ||||
|   // ========= YOU CAN EDIT AFTER THIS LINE ========= | ||||
|   App.setup(); | ||||
| } | ||||
|  | ||||
| void loop() { | ||||
|   App.loop(); | ||||
|   delay(1); | ||||
| } | ||||
| """) | ||||
|  | ||||
| INI_BASE_FORMAT = (u"""; Auto generated code by esphomeyaml | ||||
|  | ||||
| [common] | ||||
| lib_deps =  | ||||
| build_flags =  | ||||
| upload_flags =  | ||||
|  | ||||
| ; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== | ||||
| """, u""" | ||||
| ; ========= YOU CAN EDIT AFTER THIS LINE ========= | ||||
|  | ||||
| """) | ||||
|  | ||||
| INI_CONTENT_FORMAT = u"""[env:{env}] | ||||
| platform = {platform} | ||||
| board = {board} | ||||
| framework = arduino | ||||
| lib_deps = | ||||
|     {esphomeyaml_uri} | ||||
|     ${{common.lib_deps}} | ||||
| build_flags ={build_flags} | ||||
|     ${{common.build_flags}} | ||||
| """ | ||||
|  | ||||
| PLATFORM_TO_PLATFORMIO = { | ||||
|     ESP_PLATFORM_ESP32: 'espressif32', | ||||
|     ESP_PLATFORM_ESP8266: 'espressif8266' | ||||
| } | ||||
|  | ||||
|  | ||||
| def get_ini_content(config): | ||||
|     d = { | ||||
|         u'env': config[CONF_ESPHOMEYAML][CONF_NAME], | ||||
|         u'platform': PLATFORM_TO_PLATFORMIO[config[CONF_ESPHOMEYAML][CONF_PLATFORM]], | ||||
|         u'board': config[CONF_ESPHOMEYAML][CONF_BOARD], | ||||
|         u'esphomeyaml_uri': config[CONF_ESPHOMEYAML][CONF_LIBRARY_URI], | ||||
|         u'build_flags': u'', | ||||
|     } | ||||
|     if CONF_LOGGER in config: | ||||
|         build_flags = get_component(CONF_LOGGER).get_build_flags(config[CONF_LOGGER]) | ||||
|         if build_flags: | ||||
|             d[u'build_flags'] = u'\n    ' + build_flags | ||||
|     return INI_CONTENT_FORMAT.format(**d) | ||||
|  | ||||
|  | ||||
| def mkdir_p(path): | ||||
|     try: | ||||
|         os.makedirs(path) | ||||
|     except OSError as exc:  # Python >2.5 | ||||
|         if exc.errno == errno.EEXIST and os.path.isdir(path): | ||||
|             pass | ||||
|         else: | ||||
|             raise | ||||
|  | ||||
|  | ||||
| def find_begin_end(text, begin_s, end_s): | ||||
|     begin_index = text.find(begin_s) | ||||
|     if begin_index == -1: | ||||
|         raise ESPHomeYAMLError(u"Could not find auto generated code begin in file, either" | ||||
|                                u"delete the main sketch file or insert the comment again.") | ||||
|     if text.find(begin_s, begin_index + 1) != -1: | ||||
|         raise ESPHomeYAMLError(u"Found multiple auto generate code begins, don't know" | ||||
|                                u"which to chose, please remove one of them.") | ||||
|     end_index = text.find(end_s) | ||||
|     if end_index == -1: | ||||
|         raise ESPHomeYAMLError(u"Could not find auto generated code end in file, either" | ||||
|                                u"delete the main sketch file or insert the comment again.") | ||||
|     if text.find(end_s, end_index + 1) != -1: | ||||
|         raise ESPHomeYAMLError(u"Found multiple auto generate code endings, don't know" | ||||
|                                u"which to chose, please remove one of them.") | ||||
|  | ||||
|     return text[:begin_index], text[(end_index + len(end_s)):] | ||||
|  | ||||
|  | ||||
| def write_platformio_ini(content, path): | ||||
|     if os.path.isfile(path): | ||||
|         try: | ||||
|             with codecs.open(path, 'r', encoding='utf-8') as f: | ||||
|                 text = f.read() | ||||
|         except OSError: | ||||
|             raise ESPHomeYAMLError(u"Could not read ini file at {}".format(path)) | ||||
|         prev_file = text | ||||
|         content_format = find_begin_end(text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END) | ||||
|     else: | ||||
|         prev_file = None | ||||
|         mkdir_p(os.path.dirname(path)) | ||||
|         content_format = INI_BASE_FORMAT | ||||
|     full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + '\n' + \ | ||||
|         content + INI_AUTO_GENERATE_END + content_format[1] | ||||
|     if prev_file == full_file: | ||||
|         return | ||||
|     with codecs.open(path, mode='w+', encoding='utf-8') as f: | ||||
|         f.write(full_file) | ||||
|  | ||||
|  | ||||
| def write_cpp(code_s, path): | ||||
|     if os.path.isfile(path): | ||||
|         try: | ||||
|             with codecs.open(path, 'r', encoding='utf-8') as f: | ||||
|                 text = f.read() | ||||
|         except OSError: | ||||
|             raise ESPHomeYAMLError(u"Could not read C++ file at {}".format(path)) | ||||
|         prev_file = text | ||||
|         code_format = find_begin_end(text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END) | ||||
|     else: | ||||
|         prev_file = None | ||||
|         mkdir_p(os.path.dirname(path)) | ||||
|         code_format = CPP_BASE_FORMAT | ||||
|  | ||||
|     full_file = code_format[0] + CPP_AUTO_GENERATE_BEGIN + '\n' + \ | ||||
|         code_s + CPP_AUTO_GENERATE_END + code_format[1] | ||||
|     if prev_file == full_file: | ||||
|         return | ||||
|     with codecs.open(path, 'w+', encoding='utf-8') as f: | ||||
|         f.write(full_file) | ||||
							
								
								
									
										161
									
								
								esphomeyaml/yaml_util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								esphomeyaml/yaml_util.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| from __future__ import print_function | ||||
| import codecs | ||||
| import logging | ||||
| from collections import OrderedDict | ||||
|  | ||||
| import yaml | ||||
|  | ||||
| from esphomeyaml.core import ESPHomeYAMLError, HexInt, IPAddress | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class NodeListClass(list): | ||||
|     """Wrapper class to be able to add attributes on a list.""" | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class NodeStrClass(unicode): | ||||
|     """Wrapper class to be able to add attributes on a string.""" | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class SafeLineLoader(yaml.SafeLoader): | ||||
|     """Loader class that keeps track of line numbers.""" | ||||
|  | ||||
|     def compose_node(self, parent, index): | ||||
|         """Annotate a node with the first line it was seen.""" | ||||
|         last_line = self.line  # type: int | ||||
|         node = super(SafeLineLoader, self).compose_node(parent, index)  # type: yaml.nodes.Node | ||||
|         node.__line__ = last_line + 1 | ||||
|         return node | ||||
|  | ||||
|  | ||||
| def load_yaml(fname): | ||||
|     """Load a YAML file.""" | ||||
|     try: | ||||
|         with codecs.open(fname, encoding='utf-8') as conf_file: | ||||
|             return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict() | ||||
|     except yaml.YAMLError as exc: | ||||
|         _LOGGER.error(exc) | ||||
|         raise ESPHomeYAMLError(exc) | ||||
|     except UnicodeDecodeError as exc: | ||||
|         _LOGGER.error(u"Unable to read file %s: %s", fname, exc) | ||||
|         raise ESPHomeYAMLError(exc) | ||||
|  | ||||
|  | ||||
| def dump(dict_): | ||||
|     """Dump YAML to a string and remove null.""" | ||||
|     return yaml.safe_dump( | ||||
|         dict_, default_flow_style=False, allow_unicode=True) | ||||
|  | ||||
|  | ||||
| def _ordered_dict(loader, node): | ||||
|     """Load YAML mappings into an ordered dictionary to preserve key order.""" | ||||
|     loader.flatten_mapping(node) | ||||
|     nodes = loader.construct_pairs(node) | ||||
|  | ||||
|     seen = {} | ||||
|     for (key, _), (child_node, _) in zip(nodes, node.value): | ||||
|         line = child_node.start_mark.line | ||||
|  | ||||
|         try: | ||||
|             hash(key) | ||||
|         except TypeError: | ||||
|             fname = getattr(loader.stream, 'name', '') | ||||
|             raise yaml.MarkedYAMLError( | ||||
|                 context="invalid key: \"{}\"".format(key), | ||||
|                 context_mark=yaml.Mark(fname, 0, line, -1, None, None) | ||||
|             ) | ||||
|  | ||||
|         if key in seen: | ||||
|             fname = getattr(loader.stream, 'name', '') | ||||
|             _LOGGER.error( | ||||
|                 u'YAML file %s contains duplicate key "%s". ' | ||||
|                 u'Check lines %d and %d.', fname, key, seen[key], line) | ||||
|         seen[key] = line | ||||
|  | ||||
|     return _add_reference(OrderedDict(nodes), loader, node) | ||||
|  | ||||
|  | ||||
| def _construct_seq(loader, node): | ||||
|     """Add line number and file name to Load YAML sequence.""" | ||||
|     obj, = loader.construct_yaml_seq(node) | ||||
|     return _add_reference(obj, loader, node) | ||||
|  | ||||
|  | ||||
| def _add_reference(obj, loader, node): | ||||
|     """Add file reference information to an object.""" | ||||
|     if isinstance(obj, (str, unicode)): | ||||
|         obj = NodeStrClass(obj) | ||||
|     if isinstance(obj, list): | ||||
|         return obj | ||||
|     setattr(obj, '__config_file__', loader.name) | ||||
|     setattr(obj, '__line__', node.start_mark.line) | ||||
|     return obj | ||||
|  | ||||
|  | ||||
| yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _ordered_dict) | ||||
| yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, _construct_seq) | ||||
|  | ||||
|  | ||||
| # From: https://gist.github.com/miracle2k/3184458 | ||||
| # pylint: disable=redefined-outer-name | ||||
| def represent_odict(dump, tag, mapping, flow_style=None): | ||||
|     """Like BaseRepresenter.represent_mapping but does not issue the sort().""" | ||||
|     value = [] | ||||
|     node = yaml.MappingNode(tag, value, flow_style=flow_style) | ||||
|     if dump.alias_key is not None: | ||||
|         dump.represented_objects[dump.alias_key] = node | ||||
|     best_style = True | ||||
|     if hasattr(mapping, 'items'): | ||||
|         mapping = mapping.items() | ||||
|     for item_key, item_value in mapping: | ||||
|         node_key = dump.represent_data(item_key) | ||||
|         node_value = dump.represent_data(item_value) | ||||
|         if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): | ||||
|             best_style = False | ||||
|         if not (isinstance(node_value, yaml.ScalarNode) and | ||||
|                 not node_value.style): | ||||
|             best_style = False | ||||
|         value.append((node_key, node_value)) | ||||
|     if flow_style is None: | ||||
|         if dump.default_flow_style is not None: | ||||
|             node.flow_style = dump.default_flow_style | ||||
|         else: | ||||
|             node.flow_style = best_style | ||||
|     return node | ||||
|  | ||||
|  | ||||
| def unicode_representer(dumper, uni): | ||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) | ||||
|     return node | ||||
|  | ||||
|  | ||||
| def hex_int_representer(dumper, data): | ||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:int', value=str(data)) | ||||
|     return node | ||||
|  | ||||
|  | ||||
| def ipaddress_representer(dumper, data): | ||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=str(data)) | ||||
|     return node | ||||
|  | ||||
|  | ||||
| yaml.SafeDumper.add_representer( | ||||
|     OrderedDict, | ||||
|     lambda dumper, value: | ||||
|     represent_odict(dumper, 'tag:yaml.org,2002:map', value) | ||||
| ) | ||||
|  | ||||
| yaml.SafeDumper.add_representer( | ||||
|     NodeListClass, | ||||
|     lambda dumper, value: | ||||
|     dumper.represent_sequence(dumper, 'tag:yaml.org,2002:map', value) | ||||
| ) | ||||
|  | ||||
| yaml.SafeDumper.add_representer(unicode, unicode_representer) | ||||
| yaml.SafeDumper.add_representer(HexInt, hex_int_representer) | ||||
| yaml.SafeDumper.add_representer(IPAddress, ipaddress_representer) | ||||
		Reference in New Issue
	
	Block a user