mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 14:43:51 +00:00
Rename esphomeyaml to esphome (#426)
* Rename * Update * Add migration * Fix * Fix dashboard * Change test * Fixes * Code cleanup * Fix import order * Update * Automate docker builds * Shellcheck
This commit is contained in:
0
esphome/__init__.py
Normal file
0
esphome/__init__.py
Normal file
517
esphome/__main__.py
Normal file
517
esphome/__main__.py
Normal file
@@ -0,0 +1,517 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
from esphome import const, core_config, mqtt, platformio_api, wizard, writer, yaml_util
|
||||
from esphome.api.client import run_logs
|
||||
from esphome.config import get_component, iter_components, read_config, strip_default_ids
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_ESPHOME, CONF_LOGGER, \
|
||||
CONF_USE_CUSTOM_CODE
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.cpp_generator import Expression, RawStatement, add, statement
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.py_compat import IS_PY2, safe_input, text_type
|
||||
from esphome.storage_json import StorageJSON, esphome_storage_path, \
|
||||
start_update_check_thread, storage_path
|
||||
from esphome.util import run_external_command, run_external_process, safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PRE_INITIALIZE = ['esphome', 'logger', 'wifi', 'ethernet', 'ota', 'mqtt', 'web_server', 'api',
|
||||
'i2c']
|
||||
|
||||
|
||||
def get_serial_ports():
|
||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||
from serial.tools.list_ports import comports
|
||||
result = []
|
||||
for port, desc, info in comports():
|
||||
if not port:
|
||||
continue
|
||||
if "VID:PID" in info:
|
||||
result.append((port, desc))
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise ValueError
|
||||
|
||||
if len(options) == 1:
|
||||
return options[0][1]
|
||||
|
||||
safe_print(u"Found multiple options, please choose one:")
|
||||
for i, (desc, _) in enumerate(options):
|
||||
safe_print(u" [{}] {}".format(i + 1, desc))
|
||||
|
||||
while True:
|
||||
opt = safe_input('(number): ')
|
||||
if opt in options:
|
||||
opt = options.index(opt)
|
||||
break
|
||||
try:
|
||||
opt = int(opt)
|
||||
if opt < 1 or opt > len(options):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError:
|
||||
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||
options = []
|
||||
for res, desc in get_serial_ports():
|
||||
options.append((u"{} ({})".format(res, desc), res))
|
||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
||||
options.append((u"Over The Air ({})".format(CORE.address), CORE.address))
|
||||
if default == 'OTA':
|
||||
return CORE.address
|
||||
if show_mqtt and 'mqtt' in CORE.config:
|
||||
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
||||
if default == 'OTA':
|
||||
return 'MQTT'
|
||||
if default is not None:
|
||||
return default
|
||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||
return check_default
|
||||
return choose_prompt(options)
|
||||
|
||||
|
||||
def get_port_type(port):
|
||||
if port.startswith('/') or port.startswith('COM'):
|
||||
return 'SERIAL'
|
||||
if port == 'MQTT':
|
||||
return 'MQTT'
|
||||
return 'NETWORK'
|
||||
|
||||
|
||||
def run_miniterm(config, port):
|
||||
import serial
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
backtrace_state = False
|
||||
with serial.Serial(port, baudrate=baud_rate) as ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return
|
||||
if IS_PY2:
|
||||
line = raw.replace('\r', '').replace('\n', '')
|
||||
else:
|
||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
|
||||
'backslashreplace')
|
||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state)
|
||||
|
||||
|
||||
def write_cpp(config):
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
CORE.add_job(core_config.to_code, config[CONF_ESPHOME], domain='esphome')
|
||||
for domain in PRE_INITIALIZE:
|
||||
if domain == CONF_ESPHOME or domain not in config:
|
||||
continue
|
||||
CORE.add_job(get_component(domain).to_code, config[domain], domain=domain)
|
||||
|
||||
for domain, component, conf in iter_components(config):
|
||||
if domain in PRE_INITIALIZE or not hasattr(component, 'to_code'):
|
||||
continue
|
||||
CORE.add_job(component.to_code, conf, domain=domain)
|
||||
|
||||
CORE.flush_tasks()
|
||||
add(RawStatement(''))
|
||||
add(RawStatement(''))
|
||||
all_code = []
|
||||
for exp in CORE.expressions:
|
||||
if not config[CONF_ESPHOME][CONF_USE_CUSTOM_CODE]:
|
||||
if isinstance(exp, Expression) and not exp.required:
|
||||
continue
|
||||
all_code.append(text_type(statement(exp)))
|
||||
|
||||
writer.write_platformio_project()
|
||||
|
||||
code_s = indent('\n'.join(line.rstrip() for line in all_code))
|
||||
writer.write_cpp(code_s)
|
||||
return 0
|
||||
|
||||
|
||||
def compile_program(args, config):
|
||||
_LOGGER.info("Compiling app...")
|
||||
update_check = not os.getenv('ESPHOME_NO_UPDATE_CHECK', '')
|
||||
if update_check:
|
||||
thread = start_update_check_thread(esphome_storage_path(CORE.config_dir))
|
||||
rc = platformio_api.run_compile(config, args.verbose)
|
||||
if update_check:
|
||||
thread.join()
|
||||
return rc
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = os.path.join(CORE.build_path, '.pioenvs', CORE.name, 'firmware.bin')
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||
|
||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
||||
import esptool
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
|
||||
return run_external_process(*cmd)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == 'SERIAL':
|
||||
if CORE.is_esp8266:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, args.verbose, host)
|
||||
|
||||
from esphome.components import ota
|
||||
from esphome import espota2
|
||||
|
||||
if args.host_port is not None:
|
||||
host_port = args.host_port
|
||||
else:
|
||||
host_port = int(os.getenv('ESPHOME_OTA_HOST_PORT', random.randint(10000, 60000)))
|
||||
|
||||
verbose = args.verbose
|
||||
remote_port = ota.get_port(config)
|
||||
password = ota.get_auth(config)
|
||||
|
||||
storage = StorageJSON.load(storage_path())
|
||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
if res == 0:
|
||||
if storage is not None and storage.use_legacy_ota:
|
||||
storage.use_legacy_ota = False
|
||||
storage.save(storage_path())
|
||||
return res
|
||||
if storage is not None and not storage.use_legacy_ota:
|
||||
return res
|
||||
|
||||
_LOGGER.warning("OTA v2 method failed. Trying with legacy OTA...")
|
||||
return espota2.run_legacy_ota(verbose, host_port, host, remote_port, password,
|
||||
CORE.firmware_bin)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
if 'logger' not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == 'SERIAL':
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
||||
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
raise ValueError
|
||||
|
||||
|
||||
def clean_mqtt(config, args):
|
||||
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
|
||||
def setup_log(debug=False):
|
||||
log_level = logging.DEBUG if debug else logging.INFO
|
||||
logging.basicConfig(level=log_level)
|
||||
fmt = "%(levelname)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 command_wizard(args):
|
||||
return wizard.wizard(args.configuration)
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not args.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
return 0
|
||||
|
||||
|
||||
def command_compile(args, config):
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
if args.only_generate:
|
||||
_LOGGER.info(u"Successfully generated source code.")
|
||||
return 0
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=False)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
def command_run(args, config):
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=True)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
def command_clean_mqtt(args, config):
|
||||
return clean_mqtt(config, args)
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args, config):
|
||||
return mqtt.get_fingerprint(config)
|
||||
|
||||
|
||||
def command_version(args):
|
||||
safe_print(u"Version: {}".format(const.__version__))
|
||||
return 0
|
||||
|
||||
|
||||
def command_clean(args, config):
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error deleting build files: %s", err)
|
||||
return 1
|
||||
_LOGGER.info("Done!")
|
||||
return 0
|
||||
|
||||
|
||||
def command_hass_config(args, config):
|
||||
from esphome.components import mqtt as mqtt_component
|
||||
|
||||
_LOGGER.info("This is what you should put in your Home Assistant YAML configuration.")
|
||||
_LOGGER.info("Please note this is only necessary if you're not using MQTT discovery.")
|
||||
data = mqtt_component.GenerateHassConfigData(config)
|
||||
hass_config = OrderedDict()
|
||||
for domain, component, conf in iter_components(config):
|
||||
if not hasattr(component, 'to_hass_config'):
|
||||
continue
|
||||
func = getattr(component, 'to_hass_config')
|
||||
ret = func(data, conf)
|
||||
if not isinstance(ret, (list, tuple)):
|
||||
ret = [ret]
|
||||
ret = [x for x in ret if x is not None]
|
||||
domain_conf = hass_config.setdefault(domain.split('.')[0], [])
|
||||
domain_conf += ret
|
||||
|
||||
safe_print(yaml_util.dump(hass_config))
|
||||
return 0
|
||||
|
||||
|
||||
def command_dashboard(args):
|
||||
from esphome.dashboard import dashboard
|
||||
|
||||
return dashboard.start_web_server(args)
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
'config': command_config,
|
||||
'compile': command_compile,
|
||||
'upload': command_upload,
|
||||
'logs': command_logs,
|
||||
'run': command_run,
|
||||
'clean-mqtt': command_clean_mqtt,
|
||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
||||
'clean': command_clean,
|
||||
'hass-config': command_hass_config,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(prog='esphome')
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help="Internal flag to set if the command is run from the "
|
||||
"dashboard.", action='store_true')
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.')
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers.required = True
|
||||
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_compile.add_argument('--only-generate',
|
||||
help="Only generate source code, do not compile.",
|
||||
action='store_true')
|
||||
|
||||
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_USBtoUART.")
|
||||
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_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
|
||||
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/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
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.')
|
||||
|
||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.")
|
||||
|
||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
||||
|
||||
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
||||
|
||||
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
||||
|
||||
dashboard = subparsers.add_parser('dashboard',
|
||||
help="Create a simple web server for a dashboard.")
|
||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int, default=6052)
|
||||
dashboard.add_argument("--password", help="The optional password to require for all requests.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||
action='store_true')
|
||||
dashboard.add_argument("--hassio",
|
||||
help="Internal flag used to tell esphome is started as a Hass.io "
|
||||
"add-on.",
|
||||
action="store_true")
|
||||
dashboard.add_argument("--socket",
|
||||
help="Make the dashboard serve under a unix socket", type=str)
|
||||
|
||||
subparsers.add_parser('hass-config',
|
||||
help="Dump the configuration entries that should be added "
|
||||
"to Home Assistant when not using MQTT discovery.")
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
def run_esphome(argv):
|
||||
args = parse_args(argv)
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose)
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
CORE.config_path = args.configuration
|
||||
|
||||
config = read_config(args.verbose)
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
||||
if args.command in POST_CONFIG_ACTIONS:
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
safe_print(u"Unknown command {}".format(args.command))
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
return run_esphome(sys.argv)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
0
esphome/api/__init__.py
Normal file
0
esphome/api/__init__.py
Normal file
330
esphome/api/api.proto
Normal file
330
esphome/api/api.proto
Normal file
@@ -0,0 +1,330 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message HelloRequest {
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message HelloResponse {
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message ConnectRequest {
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message ConnectResponse {
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
message DisconnectRequest {
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
message DisconnectResponse {
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_core_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using esphome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
message ListEntitiesCoverResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool is_optimistic = 5;
|
||||
}
|
||||
message ListEntitiesFanResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
message ListEntitiesSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
}
|
||||
message ListEntitiesSwitchResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool optimistic = 6;
|
||||
}
|
||||
message ListEntitiesTextSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
message ListEntitiesDoneResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
enum CoverState {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
}
|
||||
CoverState state = 2;
|
||||
}
|
||||
enum FanSpeed {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
}
|
||||
message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message CoverCommandRequest {
|
||||
fixed32 key = 1;
|
||||
enum CoverCommand {
|
||||
OPEN = 0;
|
||||
CLOSE = 1;
|
||||
STOP = 2;
|
||||
}
|
||||
bool has_state = 2;
|
||||
CoverCommand command = 3;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
NONE = 0;
|
||||
ERROR = 1;
|
||||
WARN = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
VERBOSE = 5;
|
||||
VERY_VERBOSE = 6;
|
||||
}
|
||||
|
||||
message SubscribeLogsRequest {
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
|
||||
message SubscribeLogsResponse {
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
message SubscribeServiceCallsRequest {
|
||||
|
||||
}
|
||||
|
||||
message ServiceCallResponse {
|
||||
string service = 1;
|
||||
map<string, string> data = 2;
|
||||
map<string, string> data_template = 3;
|
||||
map<string, string> variables = 4;
|
||||
}
|
||||
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
|
||||
}
|
||||
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message GetTimeRequest {
|
||||
|
||||
}
|
||||
|
||||
message GetTimeResponse {
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
2484
esphome/api/api_pb2.py
Normal file
2484
esphome/api/api_pb2.py
Normal file
File diff suppressed because one or more lines are too long
495
esphome/api/client.py
Normal file
495
esphome/api/client.py
Normal file
@@ -0,0 +1,495 @@
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from typing import Optional # noqa
|
||||
from google.protobuf import message # noqa
|
||||
|
||||
from esphome import const
|
||||
import esphome.api.api_pb2 as pb
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import resolve_ip_address, indent, color
|
||||
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte, format_bytes
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIConnectionError(EsphomeError):
|
||||
pass
|
||||
|
||||
|
||||
MESSAGE_TYPE_TO_PROTO = {
|
||||
1: pb.HelloRequest,
|
||||
2: pb.HelloResponse,
|
||||
3: pb.ConnectRequest,
|
||||
4: pb.ConnectResponse,
|
||||
5: pb.DisconnectRequest,
|
||||
6: pb.DisconnectResponse,
|
||||
7: pb.PingRequest,
|
||||
8: pb.PingResponse,
|
||||
9: pb.DeviceInfoRequest,
|
||||
10: pb.DeviceInfoResponse,
|
||||
11: pb.ListEntitiesRequest,
|
||||
12: pb.ListEntitiesBinarySensorResponse,
|
||||
13: pb.ListEntitiesCoverResponse,
|
||||
14: pb.ListEntitiesFanResponse,
|
||||
15: pb.ListEntitiesLightResponse,
|
||||
16: pb.ListEntitiesSensorResponse,
|
||||
17: pb.ListEntitiesSwitchResponse,
|
||||
18: pb.ListEntitiesTextSensorResponse,
|
||||
19: pb.ListEntitiesDoneResponse,
|
||||
20: pb.SubscribeStatesRequest,
|
||||
21: pb.BinarySensorStateResponse,
|
||||
22: pb.CoverStateResponse,
|
||||
23: pb.FanStateResponse,
|
||||
24: pb.LightStateResponse,
|
||||
25: pb.SensorStateResponse,
|
||||
26: pb.SwitchStateResponse,
|
||||
27: pb.TextSensorStateResponse,
|
||||
28: pb.SubscribeLogsRequest,
|
||||
29: pb.SubscribeLogsResponse,
|
||||
30: pb.CoverCommandRequest,
|
||||
31: pb.FanCommandRequest,
|
||||
32: pb.LightCommandRequest,
|
||||
33: pb.SwitchCommandRequest,
|
||||
34: pb.SubscribeServiceCallsRequest,
|
||||
35: pb.ServiceCallResponse,
|
||||
36: pb.GetTimeRequest,
|
||||
37: pb.GetTimeResponse,
|
||||
}
|
||||
|
||||
|
||||
def _varuint_to_bytes(value):
|
||||
if value <= 0x7F:
|
||||
return byte_to_bytes(value)
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += byte_to_bytes(temp | 0x80)
|
||||
else:
|
||||
ret += byte_to_bytes(temp)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for c in value:
|
||||
val = char_to_byte(c)
|
||||
result |= (val & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
if (val & 0x80) == 0:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,not-callable
|
||||
class APIClient(threading.Thread):
|
||||
def __init__(self, address, port, password):
|
||||
threading.Thread.__init__(self)
|
||||
self._address = address # type: str
|
||||
self._port = port # type: int
|
||||
self._password = password # type: Optional[str]
|
||||
self._socket = None # type: Optional[socket.socket]
|
||||
self._socket_open_event = threading.Event()
|
||||
self._socket_write_lock = threading.Lock()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
self._keepalive = 5
|
||||
self._ping_timer = None
|
||||
self._refresh_ping()
|
||||
|
||||
self.on_disconnect = None
|
||||
self.on_connect = None
|
||||
self.on_login = None
|
||||
self.auto_reconnect = False
|
||||
self._running_event = threading.Event()
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
@property
|
||||
def stopped(self):
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def _refresh_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def func():
|
||||
self._ping_timer = None
|
||||
|
||||
if self._connected:
|
||||
try:
|
||||
self.ping()
|
||||
except APIConnectionError:
|
||||
self._fatal_error()
|
||||
else:
|
||||
self._refresh_ping()
|
||||
|
||||
self._ping_timer = threading.Timer(self._keepalive, func)
|
||||
self._ping_timer.start()
|
||||
|
||||
def _cancel_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def _close_socket(self):
|
||||
self._cancel_ping()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._socket_open_event.clear()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.stopped:
|
||||
raise ValueError
|
||||
|
||||
if self._connected and not force:
|
||||
try:
|
||||
self.disconnect()
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
self._stop_event.set()
|
||||
if not force:
|
||||
self.join()
|
||||
|
||||
def connect(self):
|
||||
if not self._running_event.wait(0.1):
|
||||
raise APIConnectionError("You need to call start() first!")
|
||||
|
||||
if self._connected:
|
||||
raise APIConnectionError("Already connected!")
|
||||
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address)
|
||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
||||
raise APIConnectionError(err)
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(10.0)
|
||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
self._socket.connect((ip, self._port))
|
||||
except socket.error as err:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = 'ESPHome v{}'.format(const.__version__)
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error()
|
||||
raise err
|
||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
||||
self._connected = True
|
||||
if self.on_connect is not None:
|
||||
self.on_connect()
|
||||
|
||||
def _check_connected(self):
|
||||
if not self._connected:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Must be connected!")
|
||||
|
||||
def login(self):
|
||||
self._check_connected()
|
||||
if self._authenticated:
|
||||
raise APIConnectionError("Already logged in!")
|
||||
|
||||
connect = pb.ConnectRequest()
|
||||
if self._password is not None:
|
||||
connect.password = self._password
|
||||
resp = self._send_message_await_response(connect, pb.ConnectResponse)
|
||||
if resp.invalid_password:
|
||||
raise APIConnectionError("Invalid password!")
|
||||
|
||||
self._authenticated = True
|
||||
if self.on_login is not None:
|
||||
self.on_login()
|
||||
|
||||
def _fatal_error(self):
|
||||
was_connected = self._connected
|
||||
|
||||
self._close_socket()
|
||||
|
||||
if was_connected and self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
|
||||
def _write(self, data): # type: (bytes) -> None
|
||||
if self._socket is None:
|
||||
raise APIConnectionError("Socket closed")
|
||||
|
||||
_LOGGER.debug("Write: %s", format_bytes(data))
|
||||
with self._socket_write_lock:
|
||||
try:
|
||||
self._socket.sendall(data)
|
||||
except socket.error as err:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Error while writing data: {}".format(err))
|
||||
|
||||
def _send_message(self, msg):
|
||||
# type: (message.Message) -> None
|
||||
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
|
||||
if isinstance(msg, klass):
|
||||
break
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
|
||||
if IS_PY2:
|
||||
req = chr(0x00)
|
||||
else:
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
req += encoded
|
||||
self._write(req)
|
||||
self._refresh_ping()
|
||||
|
||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=1):
|
||||
event = threading.Event()
|
||||
responses = []
|
||||
|
||||
def on_message(resp):
|
||||
if do_append(resp):
|
||||
responses.append(resp)
|
||||
if do_stop(resp):
|
||||
event.set()
|
||||
|
||||
self._message_handlers.append(on_message)
|
||||
self._send_message(send_msg)
|
||||
ret = event.wait(timeout)
|
||||
try:
|
||||
self._message_handlers.remove(on_message)
|
||||
except ValueError:
|
||||
pass
|
||||
if not ret:
|
||||
raise APIConnectionError("Timeout while waiting for message response!")
|
||||
return responses
|
||||
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=1):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
||||
timeout)[0]
|
||||
|
||||
def device_info(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse)
|
||||
|
||||
def ping(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||
|
||||
def disconnect(self):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=None, dump_config=False):
|
||||
self._check_authenticated()
|
||||
|
||||
def on_msg(msg):
|
||||
if isinstance(msg, pb.SubscribeLogsResponse):
|
||||
on_log(msg)
|
||||
|
||||
self._message_handlers.append(on_msg)
|
||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||
if log_level is not None:
|
||||
req.level = log_level
|
||||
self._send_message(req)
|
||||
|
||||
def _recv(self, amount):
|
||||
ret = bytes()
|
||||
if amount == 0:
|
||||
return ret
|
||||
|
||||
while len(ret) < amount:
|
||||
if self.stopped:
|
||||
raise APIConnectionError("Stopped!")
|
||||
if not self._socket_open_event.is_set():
|
||||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError:
|
||||
raise APIConnectionError("Socket was closed")
|
||||
except socket.timeout:
|
||||
continue
|
||||
except socket.error as err:
|
||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or char_to_byte(raw[-1]) & 0x80:
|
||||
raw += self._recv(1)
|
||||
return _bytes_to_varuint(raw)
|
||||
|
||||
def _run_once(self):
|
||||
if not self._socket_open_event.wait(0.1):
|
||||
return
|
||||
|
||||
# Preamble
|
||||
if char_to_byte(self._recv(1)[0]) != 0x00:
|
||||
raise APIConnectionError("Invalid preamble")
|
||||
|
||||
length = self._recv_varint()
|
||||
msg_type = self._recv_varint()
|
||||
|
||||
raw_msg = self._recv(length)
|
||||
if msg_type not in MESSAGE_TYPE_TO_PROTO:
|
||||
_LOGGER.debug("Skipping message type %s", msg_type)
|
||||
return
|
||||
|
||||
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
|
||||
msg.ParseFromString(raw_msg)
|
||||
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
self._handle_internal_messages(msg)
|
||||
self._refresh_ping()
|
||||
|
||||
def run(self):
|
||||
self._running_event.set()
|
||||
while not self.stopped:
|
||||
try:
|
||||
self._run_once()
|
||||
except APIConnectionError as err:
|
||||
if self.stopped:
|
||||
break
|
||||
if self._connected:
|
||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||
self._fatal_error()
|
||||
self._running_event.clear()
|
||||
|
||||
def _handle_internal_messages(self, msg):
|
||||
if isinstance(msg, pb.DisconnectRequest):
|
||||
self._send_message(pb.DisconnectResponse())
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._connected = False
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
elif isinstance(msg, pb.PingRequest):
|
||||
self._send_message(pb.PingResponse())
|
||||
elif isinstance(msg, pb.GetTimeRequest):
|
||||
resp = pb.GetTimeResponse()
|
||||
resp.epoch_seconds = int(time.time())
|
||||
self._send_message(resp)
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config['api']
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
|
||||
cli = APIClient(address, port, password)
|
||||
stopping = False
|
||||
retry_timer = []
|
||||
|
||||
has_connects = []
|
||||
|
||||
def try_connect(tries=0, is_disconnect=True):
|
||||
if stopping:
|
||||
return
|
||||
|
||||
if is_disconnect:
|
||||
_LOGGER.warning(u"Disconnected from API.")
|
||||
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
|
||||
error = None
|
||||
try:
|
||||
cli.connect()
|
||||
cli.login()
|
||||
except APIConnectionError as err: # noqa
|
||||
error = err
|
||||
|
||||
if error is None:
|
||||
_LOGGER.info("Successfully connected to %s", address)
|
||||
return
|
||||
|
||||
wait_time = min(2**tries, 300)
|
||||
if not has_connects:
|
||||
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error, wait_time)
|
||||
else:
|
||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error, wait_time)
|
||||
timer = threading.Timer(wait_time, functools.partial(try_connect, tries + 1, is_disconnect))
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color('white', '(Message skipped because it was too big to fit in '
|
||||
'TCP buffer - This is only cosmetic)')
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
try:
|
||||
cli.subscribe_logs(on_log, dump_config=not has_connects)
|
||||
has_connects.append(True)
|
||||
except APIConnectionError:
|
||||
cli.disconnect()
|
||||
|
||||
cli.on_disconnect = try_connect
|
||||
cli.on_login = on_login
|
||||
cli.start()
|
||||
|
||||
try:
|
||||
try_connect(is_disconnect=False)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
stopping = True
|
||||
cli.stop(True)
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
return 0
|
||||
367
esphome/automation.py
Normal file
367
esphome/automation.py
Normal file
@@ -0,0 +1,367 @@
|
||||
import copy
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, \
|
||||
CONF_BELOW, CONF_CONDITION, CONF_CONDITION_ID, CONF_DELAY, CONF_ELSE, CONF_ID, CONF_IF, \
|
||||
CONF_LAMBDA, CONF_OR, CONF_RANGE, CONF_THEN, CONF_TRIGGER_ID, CONF_WHILE
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, TemplateArguments, add, get_variable, \
|
||||
process_lambda, templatable
|
||||
from esphome.cpp_types import Action, App, Component, PollingComponent, Trigger, bool_, \
|
||||
esphome_ns, float_, uint32, void
|
||||
from esphome.util import ServiceRegistry
|
||||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
validator = vol.All(*validators)
|
||||
|
||||
def validate(value):
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
return validator({CONF_ID: value})
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
def validate_recursive_condition(value):
|
||||
is_list = isinstance(value, list)
|
||||
value = cv.ensure_list()(value)[:]
|
||||
for i, item in enumerate(value):
|
||||
path = [i] if is_list else []
|
||||
item = copy.deepcopy(item)
|
||||
if not isinstance(item, dict):
|
||||
raise vol.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item),
|
||||
path)
|
||||
key = next((x for x in item if x != CONF_CONDITION_ID), None)
|
||||
if key is None:
|
||||
raise vol.Invalid(u"Key missing from action! Got {}".format(item), path)
|
||||
if key not in CONDITION_REGISTRY:
|
||||
raise vol.Invalid(u"Unable to find condition with the name '{}', is the "
|
||||
u"component loaded?".format(key), path + [key])
|
||||
item.setdefault(CONF_CONDITION_ID, None)
|
||||
key2 = next((x for x in item if x not in (CONF_CONDITION_ID, key)), None)
|
||||
if key2 is not None:
|
||||
raise vol.Invalid(u"Cannot have two conditions in one item. Key '{}' overrides '{}'! "
|
||||
u"Did you forget to indent the block inside the condition?"
|
||||
u"".format(key, key2), path)
|
||||
validator = CONDITION_REGISTRY[key][0]
|
||||
try:
|
||||
condition = validator(item[key])
|
||||
except vol.Invalid as err:
|
||||
err.prepend(path)
|
||||
raise err
|
||||
value[i] = {
|
||||
CONF_CONDITION_ID: cv.declare_variable_id(Condition)(item[CONF_CONDITION_ID]),
|
||||
key: condition,
|
||||
}
|
||||
return value
|
||||
|
||||
|
||||
def validate_recursive_action(value):
|
||||
is_list = isinstance(value, list)
|
||||
if not is_list:
|
||||
value = [value]
|
||||
for i, item in enumerate(value):
|
||||
path = [i] if is_list else []
|
||||
item = copy.deepcopy(item)
|
||||
if not isinstance(item, dict):
|
||||
raise vol.Invalid(u"Action must consist of key-value mapping! Got {}".format(item),
|
||||
path)
|
||||
key = next((x for x in item if x != CONF_ACTION_ID), None)
|
||||
if key is None:
|
||||
raise vol.Invalid(u"Key missing from action! Got {}".format(item), path)
|
||||
if key not in ACTION_REGISTRY:
|
||||
raise vol.Invalid(u"Unable to find action with the name '{}', is the component loaded?"
|
||||
u"".format(key), path + [key])
|
||||
item.setdefault(CONF_ACTION_ID, None)
|
||||
key2 = next((x for x in item if x not in (CONF_ACTION_ID, key)), None)
|
||||
if key2 is not None:
|
||||
raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! "
|
||||
u"Did you forget to indent the block inside the action?"
|
||||
u"".format(key, key2), path)
|
||||
validator = ACTION_REGISTRY[key][0]
|
||||
try:
|
||||
action = validator(item[key])
|
||||
except vol.Invalid as err:
|
||||
err.prepend(path)
|
||||
raise err
|
||||
value[i] = {
|
||||
CONF_ACTION_ID: cv.declare_variable_id(Action)(item[CONF_ACTION_ID]),
|
||||
key: action,
|
||||
}
|
||||
return value
|
||||
|
||||
|
||||
ACTION_REGISTRY = ServiceRegistry()
|
||||
CONDITION_REGISTRY = ServiceRegistry()
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
DelayAction = esphome_ns.class_('DelayAction', Action, Component)
|
||||
LambdaAction = esphome_ns.class_('LambdaAction', Action)
|
||||
IfAction = esphome_ns.class_('IfAction', Action)
|
||||
WhileAction = esphome_ns.class_('WhileAction', Action)
|
||||
UpdateComponentAction = esphome_ns.class_('UpdateComponentAction', Action)
|
||||
Automation = esphome_ns.class_('Automation')
|
||||
|
||||
Condition = esphome_ns.class_('Condition')
|
||||
AndCondition = esphome_ns.class_('AndCondition', Condition)
|
||||
OrCondition = esphome_ns.class_('OrCondition', Condition)
|
||||
RangeCondition = esphome_ns.class_('RangeCondition', Condition)
|
||||
LambdaCondition = esphome_ns.class_('LambdaCondition', Condition)
|
||||
|
||||
|
||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
if extra_schema is None:
|
||||
extra_schema = {}
|
||||
if isinstance(extra_schema, vol.Schema):
|
||||
extra_schema = extra_schema.schema
|
||||
schema = AUTOMATION_SCHEMA.extend(extra_schema)
|
||||
|
||||
def validator_(value):
|
||||
if isinstance(value, list):
|
||||
try:
|
||||
# First try as a sequence of actions
|
||||
return [schema({CONF_THEN: value})]
|
||||
except vol.Invalid as err:
|
||||
if err.path and err.path[0] == CONF_THEN:
|
||||
err.path.pop(0)
|
||||
|
||||
# Next try as a sequence of automations
|
||||
try:
|
||||
return vol.Schema([schema])(value)
|
||||
except vol.Invalid as err2:
|
||||
if 'Unable to find action' in str(err):
|
||||
raise err2
|
||||
raise vol.MultipleInvalid([err, err2])
|
||||
elif isinstance(value, dict):
|
||||
if CONF_THEN in value:
|
||||
return [schema(value)]
|
||||
return [schema({CONF_THEN: value})]
|
||||
# This should only happen with invalid configs, but let's have a nice error message.
|
||||
return [schema(value)]
|
||||
|
||||
def validator(value):
|
||||
value = validator_(value)
|
||||
if extra_validators is not None:
|
||||
value = vol.Schema([extra_validators])(value)
|
||||
if single:
|
||||
if len(value) != 1:
|
||||
raise vol.Invalid("Cannot have more than 1 automation for templates")
|
||||
return value[0]
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
AUTOMATION_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation),
|
||||
vol.Optional(CONF_IF): validate_recursive_condition,
|
||||
vol.Required(CONF_THEN): validate_recursive_action,
|
||||
})
|
||||
|
||||
AND_CONDITION_SCHEMA = validate_recursive_condition
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_AND, AND_CONDITION_SCHEMA)
|
||||
def and_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||
for conditions in build_conditions(config, arg_type):
|
||||
yield
|
||||
rhs = AndCondition.new(template_arg, conditions)
|
||||
type = AndCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
OR_CONDITION_SCHEMA = validate_recursive_condition
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_OR, OR_CONDITION_SCHEMA)
|
||||
def or_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||
for conditions in build_conditions(config, arg_type):
|
||||
yield
|
||||
rhs = OrCondition.new(template_arg, conditions)
|
||||
type = OrCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
RANGE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||
vol.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_RANGE, RANGE_CONDITION_SCHEMA)
|
||||
def range_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||
for conditions in build_conditions(config, arg_type):
|
||||
yield
|
||||
rhs = RangeCondition.new(template_arg, conditions)
|
||||
type = RangeCondition.template(template_arg)
|
||||
condition = Pvariable(condition_id, rhs, type=type)
|
||||
if CONF_ABOVE in config:
|
||||
for template_ in templatable(config[CONF_ABOVE], arg_type, float_):
|
||||
yield
|
||||
condition.set_min(template_)
|
||||
if CONF_BELOW in config:
|
||||
for template_ in templatable(config[CONF_BELOW], arg_type, float_):
|
||||
yield
|
||||
condition.set_max(template_)
|
||||
yield condition
|
||||
|
||||
|
||||
DELAY_ACTION_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds)
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_DELAY, DELAY_ACTION_SCHEMA)
|
||||
def delay_action_to_code(config, action_id, arg_type, template_arg):
|
||||
rhs = App.register_component(DelayAction.new(template_arg))
|
||||
type = DelayAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for template_ in templatable(config, arg_type, uint32):
|
||||
yield
|
||||
add(action.set_delay(template_))
|
||||
yield action
|
||||
|
||||
|
||||
IF_ACTION_SCHEMA = vol.All({
|
||||
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
||||
vol.Optional(CONF_THEN): validate_recursive_action,
|
||||
vol.Optional(CONF_ELSE): validate_recursive_action,
|
||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_IF, IF_ACTION_SCHEMA)
|
||||
def if_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for conditions in build_conditions(config[CONF_CONDITION], arg_type):
|
||||
yield None
|
||||
rhs = IfAction.new(template_arg, conditions)
|
||||
type = IfAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_THEN in config:
|
||||
for actions in build_actions(config[CONF_THEN], arg_type):
|
||||
yield None
|
||||
add(action.add_then(actions))
|
||||
if CONF_ELSE in config:
|
||||
for actions in build_actions(config[CONF_ELSE], arg_type):
|
||||
yield None
|
||||
add(action.add_else(actions))
|
||||
yield action
|
||||
|
||||
|
||||
WHILE_ACTION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
||||
vol.Required(CONF_THEN): validate_recursive_action,
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_WHILE, WHILE_ACTION_SCHEMA)
|
||||
def while_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for conditions in build_conditions(config[CONF_CONDITION], arg_type):
|
||||
yield None
|
||||
rhs = WhileAction.new(template_arg, conditions)
|
||||
type = WhileAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for actions in build_actions(config[CONF_THEN], arg_type):
|
||||
yield None
|
||||
add(action.add_then(actions))
|
||||
yield action
|
||||
|
||||
|
||||
LAMBDA_ACTION_SCHEMA = cv.lambda_
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LAMBDA, LAMBDA_ACTION_SCHEMA)
|
||||
def lambda_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for lambda_ in process_lambda(config, [(arg_type, 'x')], return_type=void):
|
||||
yield None
|
||||
rhs = LambdaAction.new(template_arg, lambda_)
|
||||
type = LambdaAction.template(template_arg)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
LAMBDA_CONDITION_SCHEMA = cv.lambda_
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_LAMBDA, LAMBDA_CONDITION_SCHEMA)
|
||||
def lambda_condition_to_code(config, condition_id, arg_type, template_arg):
|
||||
for lambda_ in process_lambda(config, [(arg_type, 'x')], return_type=bool_):
|
||||
yield
|
||||
rhs = LambdaCondition.new(template_arg, lambda_)
|
||||
type = LambdaCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COMPONENT_UPDATE = 'component.update'
|
||||
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(PollingComponent),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COMPONENT_UPDATE, COMPONENT_UPDATE_ACTION_SCHEMA)
|
||||
def component_update_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = UpdateComponentAction.new(template_arg, var)
|
||||
type = UpdateComponentAction.template(template_arg)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
def build_action(full_config, arg_type):
|
||||
action_id = full_config[CONF_ACTION_ID]
|
||||
key, config = next((k, v) for k, v in full_config.items() if k in ACTION_REGISTRY)
|
||||
|
||||
builder = ACTION_REGISTRY[key][1]
|
||||
template_arg = TemplateArguments(arg_type)
|
||||
for result in builder(config, action_id, arg_type, template_arg):
|
||||
yield None
|
||||
yield result
|
||||
|
||||
|
||||
def build_actions(config, arg_type):
|
||||
actions = []
|
||||
for conf in config:
|
||||
for action in build_action(conf, arg_type):
|
||||
yield None
|
||||
actions.append(action)
|
||||
yield actions
|
||||
|
||||
|
||||
def build_condition(full_config, arg_type):
|
||||
action_id = full_config[CONF_CONDITION_ID]
|
||||
key, config = next((k, v) for k, v in full_config.items() if k in CONDITION_REGISTRY)
|
||||
|
||||
builder = CONDITION_REGISTRY[key][1]
|
||||
template_arg = TemplateArguments(arg_type)
|
||||
for result in builder(config, action_id, arg_type, template_arg):
|
||||
yield None
|
||||
yield result
|
||||
|
||||
|
||||
def build_conditions(config, arg_type):
|
||||
conditions = []
|
||||
for conf in config:
|
||||
for condition in build_condition(conf, arg_type):
|
||||
yield None
|
||||
conditions.append(condition)
|
||||
yield conditions
|
||||
|
||||
|
||||
def build_automation_(trigger, arg_type, config):
|
||||
rhs = App.make_automation(TemplateArguments(arg_type), trigger)
|
||||
type = Automation.template(arg_type)
|
||||
obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type)
|
||||
if CONF_IF in config:
|
||||
conditions = None
|
||||
for conditions in build_conditions(config[CONF_IF], arg_type):
|
||||
yield None
|
||||
add(obj.add_conditions(conditions))
|
||||
actions = None
|
||||
for actions in build_actions(config[CONF_THEN], arg_type):
|
||||
yield None
|
||||
add(obj.add_actions(actions))
|
||||
yield obj
|
||||
|
||||
|
||||
def build_automation(trigger, arg_type, config):
|
||||
CORE.add_job(build_automation_, trigger, arg_type, config)
|
||||
0
esphome/components/__init__.py
Normal file
0
esphome/components/__init__.py
Normal file
27
esphome/components/ads1115.py
Normal file
27
esphome/components/ads1115.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
MULTI_CONF = True
|
||||
|
||||
ADS1115Component = sensor.sensor_ns.class_('ADS1115Component', Component, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(ADS1115Component),
|
||||
vol.Required(CONF_ADDRESS): cv.i2c_address,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_ads1115_component(config[CONF_ADDRESS])
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
setup_component(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ADS1115_SENSOR'
|
||||
33
esphome/components/apds9960.py
Normal file
33
esphome/components/apds9960.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = 'apds9960_id'
|
||||
APDS9960 = sensor.sensor_ns.class_('APDS9960', PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(APDS9960),
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_apds9960(config.get(CONF_UPDATE_INTERVAL))
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_ADDRESS in config:
|
||||
add(var.set_address(config[CONF_ADDRESS]))
|
||||
|
||||
setup_component(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_APDS9960'
|
||||
88
esphome/components/api.py
Normal file
88
esphome/components/api.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, Component, StoringController, esphome_ns
|
||||
|
||||
api_ns = esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', Component, StoringController)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', Action)
|
||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(APIServer),
|
||||
vol.Optional(CONF_PORT, default=6053): cv.port,
|
||||
vol.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.init_api_server()
|
||||
api = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if config[CONF_PORT] != 6053:
|
||||
add(api.set_port(config[CONF_PORT]))
|
||||
if config.get(CONF_PASSWORD):
|
||||
add(api.set_password(config[CONF_PASSWORD]))
|
||||
if CONF_REBOOT_TIMEOUT in config:
|
||||
add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
setup_component(api, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_API'
|
||||
|
||||
|
||||
def lib_deps(config):
|
||||
if CORE.is_esp32:
|
||||
return 'AsyncTCP@1.0.1'
|
||||
if CORE.is_esp8266:
|
||||
return 'ESPAsyncTCP@1.1.3'
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
CONF_HOMEASSISTANT_SERVICE = 'homeassistant.service'
|
||||
HOMEASSISTANT_SERVIC_ACTION_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.use_variable_id(APIServer),
|
||||
vol.Required(CONF_SERVICE): cv.string,
|
||||
vol.Optional(CONF_DATA): vol.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
vol.Optional(CONF_DATA_TEMPLATE): vol.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
vol.Optional(CONF_VARIABLES): vol.Schema({
|
||||
cv.string: cv.lambda_,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_HOMEASSISTANT_SERVICE, HOMEASSISTANT_SERVIC_ACTION_SCHEMA)
|
||||
def homeassistant_service_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_home_assistant_service_call_action(template_arg)
|
||||
type = HomeAssistantServiceCallAction.template(arg_type)
|
||||
act = Pvariable(action_id, rhs, type=type)
|
||||
add(act.set_service(config[CONF_SERVICE]))
|
||||
if CONF_DATA in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
||||
add(act.set_data(datas))
|
||||
if CONF_DATA_TEMPLATE in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
||||
add(act.set_data_template(datas))
|
||||
if CONF_VARIABLES in config:
|
||||
datas = []
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
for value_ in process_lambda(value, []):
|
||||
yield None
|
||||
datas.append(TemplatableKeyValuePair(key, value_))
|
||||
add(act.set_variables(datas))
|
||||
yield act
|
||||
329
esphome/components/binary_sensor/__init__.py
Normal file
329
esphome/components/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,329 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation, core
|
||||
from esphome.automation import CONDITION_REGISTRY, Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DELAYED_OFF, CONF_DELAYED_ON, CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||
CONF_HEARTBEAT, CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERT, CONF_INVERTED, \
|
||||
CONF_LAMBDA, CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_MQTT_ID, CONF_ON_CLICK, \
|
||||
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda
|
||||
from esphome.cpp_types import App, Component, Nameable, NoArg, Trigger, bool_, esphome_ns, optional
|
||||
from esphome.py_compat import string_types
|
||||
|
||||
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'
|
||||
]
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
binary_sensor_ns = esphome_ns.namespace('binary_sensor')
|
||||
BinarySensor = binary_sensor_ns.class_('BinarySensor', Nameable)
|
||||
BinarySensorPtr = BinarySensor.operator('ptr')
|
||||
MQTTBinarySensorComponent = binary_sensor_ns.class_('MQTTBinarySensorComponent', mqtt.MQTTComponent)
|
||||
|
||||
# Triggers
|
||||
PressTrigger = binary_sensor_ns.class_('PressTrigger', Trigger.template(NoArg))
|
||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', Trigger.template(NoArg))
|
||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', Trigger.template(NoArg))
|
||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', Trigger.template(NoArg))
|
||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', Trigger.template(NoArg), Component)
|
||||
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
|
||||
StateTrigger = binary_sensor_ns.class_('StateTrigger', Trigger.template(bool_))
|
||||
|
||||
# Condition
|
||||
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_('Filter')
|
||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, Component)
|
||||
HeartbeatFilter = binary_sensor_ns.class_('HeartbeatFilter', Filter, Component)
|
||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
||||
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
|
||||
|
||||
FILTER_KEYS = [CONF_INVERT, CONF_DELAYED_ON, CONF_DELAYED_OFF, CONF_LAMBDA, CONF_HEARTBEAT]
|
||||
|
||||
FILTERS_SCHEMA = cv.ensure_list({
|
||||
vol.Optional(CONF_INVERT): None,
|
||||
vol.Optional(CONF_DELAYED_ON): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_DELAYED_OFF): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
|
||||
vol.Optional(CONF_HEARTBEAT): cv.invalid("The heartbeat filter has been removed in 1.11.0"),
|
||||
}, cv.has_exactly_one_key(*FILTER_KEYS))
|
||||
|
||||
MULTI_CLICK_TIMING_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_STATE): cv.boolean,
|
||||
vol.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
|
||||
})
|
||||
|
||||
|
||||
def parse_multi_click_timing_str(value):
|
||||
if not isinstance(value, string_types):
|
||||
return value
|
||||
|
||||
parts = value.lower().split(' ')
|
||||
if len(parts) != 5:
|
||||
raise vol.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
|
||||
"".format(len(parts)))
|
||||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except vol.Invalid:
|
||||
raise vol.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
|
||||
if parts[1] != 'for':
|
||||
raise vol.Invalid(u"Second word must be 'for', got {}".format(parts[1]))
|
||||
|
||||
if parts[2] == 'at':
|
||||
if parts[3] == 'least':
|
||||
key = CONF_MIN_LENGTH
|
||||
elif parts[3] == 'most':
|
||||
key = CONF_MAX_LENGTH
|
||||
else:
|
||||
raise vol.Invalid(u"Third word after at must either be 'least' or 'most', got {}"
|
||||
u"".format(parts[3]))
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except vol.Invalid as err:
|
||||
raise vol.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err))
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
key: str(length)
|
||||
}
|
||||
|
||||
if parts[3] != 'to':
|
||||
raise vol.Invalid("Multi click grammar: 4th word must be 'to'")
|
||||
|
||||
try:
|
||||
min_length = cv.positive_time_period_milliseconds(parts[2])
|
||||
except vol.Invalid as err:
|
||||
raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
||||
|
||||
try:
|
||||
max_length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except vol.Invalid as err:
|
||||
raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
||||
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
CONF_MIN_LENGTH: str(min_length),
|
||||
CONF_MAX_LENGTH: str(max_length)
|
||||
}
|
||||
|
||||
|
||||
def validate_multi_click_timing(value):
|
||||
if not isinstance(value, list):
|
||||
raise vol.Invalid("Timing option must be a *list* of times!")
|
||||
timings = []
|
||||
state = None
|
||||
for i, v_ in enumerate(value):
|
||||
v_ = MULTI_CLICK_TIMING_SCHEMA(v_)
|
||||
min_length = v_.get(CONF_MIN_LENGTH)
|
||||
max_length = v_.get(CONF_MAX_LENGTH)
|
||||
if min_length is None and max_length is None:
|
||||
raise vol.Invalid("At least one of min_length and max_length is required!")
|
||||
if min_length is None and max_length is not None:
|
||||
min_length = core.TimePeriodMilliseconds(milliseconds=0)
|
||||
|
||||
new_state = v_.get(CONF_STATE, not state)
|
||||
if new_state == state:
|
||||
raise vol.Invalid("Timings must have alternating state. Indices {} and {} have "
|
||||
"the same state {}".format(i, i + 1, state))
|
||||
if max_length is not None and max_length < min_length:
|
||||
raise vol.Invalid("Max length ({}) must be larger than min length ({})."
|
||||
"".format(max_length, min_length))
|
||||
|
||||
state = new_state
|
||||
tim = {
|
||||
CONF_STATE: new_state,
|
||||
CONF_MIN_LENGTH: min_length,
|
||||
}
|
||||
if max_length is not None:
|
||||
tim[CONF_MAX_LENGTH] = max_length
|
||||
timings.append(tim)
|
||||
return timings
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTBinarySensorComponent),
|
||||
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
vol.Optional(CONF_FILTERS): FILTERS_SCHEMA,
|
||||
vol.Optional(CONF_ON_PRESS): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PressTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ReleaseTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ClickTrigger),
|
||||
vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger),
|
||||
vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MultiClickTrigger),
|
||||
vol.Required(CONF_TIMING): vol.All([parse_multi_click_timing_str],
|
||||
validate_multi_click_timing),
|
||||
vol.Optional(CONF_INVALID_COOLDOWN): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ON_STATE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StateTrigger),
|
||||
}),
|
||||
|
||||
vol.Optional(CONF_INVERTED): cv.invalid(
|
||||
"The inverted binary_sensor property has been replaced by the "
|
||||
"new 'invert' binary sensor filter. Please see "
|
||||
"https://esphome.io/components/binary_sensor/index.html."
|
||||
),
|
||||
})
|
||||
|
||||
BINARY_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BINARY_SENSOR_SCHEMA.schema)
|
||||
|
||||
|
||||
def setup_filter(config):
|
||||
if CONF_INVERT in config:
|
||||
yield InvertFilter.new()
|
||||
elif CONF_DELAYED_OFF in config:
|
||||
yield App.register_component(DelayedOffFilter.new(config[CONF_DELAYED_OFF]))
|
||||
elif CONF_DELAYED_ON in config:
|
||||
yield App.register_component(DelayedOnFilter.new(config[CONF_DELAYED_ON]))
|
||||
elif CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(bool_, 'x')],
|
||||
return_type=optional.template(bool_)):
|
||||
yield None
|
||||
yield LambdaFilter.new(lambda_)
|
||||
|
||||
|
||||
def setup_filters(config):
|
||||
filters = []
|
||||
for conf in config:
|
||||
for filter in setup_filter(conf):
|
||||
yield None
|
||||
filters.append(filter)
|
||||
yield filters
|
||||
|
||||
|
||||
def setup_binary_sensor_core_(binary_sensor_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(binary_sensor_var.set_internal(CONF_INTERNAL))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
add(binary_sensor_var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_INVERTED in config:
|
||||
add(binary_sensor_var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_FILTERS in config:
|
||||
for filters in setup_filters(config[CONF_FILTERS]):
|
||||
yield
|
||||
add(binary_sensor_var.add_filters(filters))
|
||||
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
rhs = binary_sensor_var.make_press_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_RELEASE, []):
|
||||
rhs = binary_sensor_var.make_release_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
rhs = binary_sensor_var.make_click_trigger(conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
|
||||
rhs = binary_sensor_var.make_double_click_trigger(conf[CONF_MIN_LENGTH],
|
||||
conf[CONF_MAX_LENGTH])
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||
timings = []
|
||||
for tim in conf[CONF_TIMING]:
|
||||
timings.append(StructInitializer(
|
||||
MultiClickTriggerEvent,
|
||||
('state', tim[CONF_STATE]),
|
||||
('min_length', tim[CONF_MIN_LENGTH]),
|
||||
('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||
))
|
||||
rhs = App.register_component(binary_sensor_var.make_multi_click_trigger(timings))
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
rhs = binary_sensor_var.make_state_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, bool_, conf)
|
||||
|
||||
setup_mqtt_component(binary_sensor_var.Pget_mqtt(), config)
|
||||
|
||||
|
||||
def setup_binary_sensor(binary_sensor_obj, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
binary_sensor_obj = Pvariable(config[CONF_ID], binary_sensor_obj, has_side_effects=True)
|
||||
CORE.add_job(setup_binary_sensor_core_, binary_sensor_obj, config)
|
||||
|
||||
|
||||
def register_binary_sensor(var, config):
|
||||
binary_sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True)
|
||||
add(App.register_binary_sensor(binary_sensor_var))
|
||||
CORE.add_job(setup_binary_sensor_core_, binary_sensor_var, config)
|
||||
|
||||
|
||||
def core_to_hass_config(data, config):
|
||||
ret = mqtt.build_hass_config(data, 'binary_sensor', config,
|
||||
include_state=True, include_command=False)
|
||||
if ret is None:
|
||||
return None
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
ret['device_class'] = config[CONF_DEVICE_CLASS]
|
||||
return ret
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BINARY_SENSOR'
|
||||
|
||||
CONF_BINARY_SENSOR_IS_ON = 'binary_sensor.is_on'
|
||||
BINARY_SENSOR_IS_ON_CONDITION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
|
||||
})
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_ON, BINARY_SENSOR_IS_ON_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_binary_sensor_is_on_condition(template_arg)
|
||||
type = BinarySensorCondition.template(arg_type)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_BINARY_SENSOR_IS_OFF = 'binary_sensor.is_off'
|
||||
BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
|
||||
})
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_OFF, BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_binary_sensor_is_off_condition(template_arg)
|
||||
type = BinarySensorCondition.template(arg_type)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
36
esphome/components/binary_sensor/apds9960.py
Normal file
36
esphome/components/binary_sensor/apds9960.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, sensor
|
||||
from esphome.components.apds9960 import APDS9960, CONF_APDS9960_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DIRECTION, CONF_NAME
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
APDS9960GestureDirectionBinarySensor = sensor.sensor_ns.class_(
|
||||
'APDS9960GestureDirectionBinarySensor', binary_sensor.BinarySensor)
|
||||
|
||||
DIRECTIONS = {
|
||||
'UP': 'make_up_direction',
|
||||
'DOWN': 'make_down_direction',
|
||||
'LEFT': 'make_left_direction',
|
||||
'RIGHT': 'make_right_direction',
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(APDS9960GestureDirectionBinarySensor),
|
||||
vol.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_APDS9960_ID]):
|
||||
yield
|
||||
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
||||
rhs = func(config[CONF_NAME])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
39
esphome/components/binary_sensor/custom.py
Normal file
39
esphome/components/binary_sensor/custom.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable, add, process_lambda, variable
|
||||
from esphome.cpp_types import std_vector
|
||||
|
||||
CustomBinarySensorConstructor = binary_sensor.binary_sensor_ns.class_(
|
||||
'CustomBinarySensorConstructor')
|
||||
|
||||
PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CustomBinarySensorConstructor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Required(CONF_BINARY_SENSORS):
|
||||
cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(binary_sensor.BinarySensor),
|
||||
})),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=std_vector.template(binary_sensor.BinarySensorPtr)):
|
||||
yield
|
||||
|
||||
rhs = CustomBinarySensorConstructor(template_)
|
||||
custom = variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_BINARY_SENSORS]):
|
||||
var = Pvariable(conf[CONF_ID], custom.get_binary_sensor(i))
|
||||
add(var.set_name(conf[CONF_NAME]))
|
||||
binary_sensor.setup_binary_sensor(var, conf)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_CUSTOM_BINARY_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [binary_sensor.core_to_hass_config(data, sens) for sens in config[CONF_BINARY_SENSORS]]
|
||||
29
esphome/components/binary_sensor/esp32_ble_tracker.py
Normal file
29
esphome/components/binary_sensor/esp32_ble_tracker.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESP32BLETracker, \
|
||||
make_address_array
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_NAME
|
||||
from esphome.cpp_generator import get_variable
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
ESP32BLEPresenceDevice = esphome_ns.class_('ESP32BLEPresenceDevice', binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32BLEPresenceDevice),
|
||||
vol.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_variable_id(ESP32BLETracker)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_ESP32_BLE_ID]):
|
||||
yield
|
||||
rhs = hub.make_presence_sensor(config[CONF_NAME], make_address_array(config[CONF_MAC_ADDRESS]))
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
62
esphome/components/binary_sensor/esp32_touch.py
Normal file
62
esphome/components/binary_sensor/esp32_touch.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.esp32_touch import ESP32TouchComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32
|
||||
from esphome.cpp_generator import get_variable
|
||||
from esphome.cpp_types import global_ns
|
||||
from esphome.pins import validate_gpio_pin
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
DEPENDENCIES = ['esp32_touch']
|
||||
|
||||
CONF_ESP32_TOUCH_ID = 'esp32_touch_id'
|
||||
|
||||
TOUCH_PADS = {
|
||||
4: global_ns.TOUCH_PAD_NUM0,
|
||||
0: global_ns.TOUCH_PAD_NUM1,
|
||||
2: global_ns.TOUCH_PAD_NUM2,
|
||||
15: global_ns.TOUCH_PAD_NUM3,
|
||||
13: global_ns.TOUCH_PAD_NUM4,
|
||||
12: global_ns.TOUCH_PAD_NUM5,
|
||||
14: global_ns.TOUCH_PAD_NUM6,
|
||||
27: global_ns.TOUCH_PAD_NUM7,
|
||||
33: global_ns.TOUCH_PAD_NUM8,
|
||||
32: global_ns.TOUCH_PAD_NUM9,
|
||||
}
|
||||
|
||||
|
||||
def validate_touch_pad(value):
|
||||
value = validate_gpio_pin(value)
|
||||
if value not in TOUCH_PADS:
|
||||
raise vol.Invalid("Pin {} does not support touch pads.".format(value))
|
||||
return value
|
||||
|
||||
|
||||
ESP32TouchBinarySensor = binary_sensor.binary_sensor_ns.class_('ESP32TouchBinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32TouchBinarySensor),
|
||||
vol.Required(CONF_PIN): validate_touch_pad,
|
||||
vol.Required(CONF_THRESHOLD): cv.uint16_t,
|
||||
cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_variable_id(ESP32TouchComponent),
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = None
|
||||
for hub in get_variable(config[CONF_ESP32_TOUCH_ID]):
|
||||
yield
|
||||
touch_pad = TOUCH_PADS[config[CONF_PIN]]
|
||||
rhs = hub.make_touch_pad(config[CONF_NAME], touch_pad, config[CONF_THRESHOLD])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
35
esphome/components/binary_sensor/gpio.py
Normal file
35
esphome/components/binary_sensor/gpio.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_PIN
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
GPIOBinarySensorComponent = binary_sensor.binary_sensor_ns.class_('GPIOBinarySensorComponent',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(GPIOBinarySensorComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_input_pin_schema
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
pin = None
|
||||
for pin in gpio_input_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_gpio_binary_sensor(config[CONF_NAME], pin)
|
||||
gpio = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(gpio, config)
|
||||
setup_component(gpio, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_GPIO_BINARY_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
30
esphome/components/binary_sensor/homeassistant.py
Normal file
30
esphome/components/binary_sensor/homeassistant.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENTITY_ID, CONF_ID, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DEPENDENCIES = ['api']
|
||||
|
||||
HomeassistantBinarySensor = binary_sensor.binary_sensor_ns.class_('HomeassistantBinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HomeassistantBinarySensor),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_homeassistant_binary_sensor(config[CONF_NAME], config[CONF_ENTITY_ID])
|
||||
subs = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(subs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HOMEASSISTANT_BINARY_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
33
esphome/components/binary_sensor/nextion.py
Normal file
33
esphome/components/binary_sensor/nextion.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, display
|
||||
from esphome.components.display.nextion import Nextion
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COMPONENT_ID, CONF_NAME, CONF_PAGE_ID
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
|
||||
CONF_NEXTION_ID = 'nextion_id'
|
||||
|
||||
NextionTouchComponent = display.display_ns.class_('NextionTouchComponent',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(NextionTouchComponent),
|
||||
vol.Required(CONF_PAGE_ID): cv.uint8_t,
|
||||
vol.Required(CONF_COMPONENT_ID): cv.uint8_t,
|
||||
cv.GenerateID(CONF_NEXTION_ID): cv.use_variable_id(Nextion)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_NEXTION_ID]):
|
||||
yield
|
||||
rhs = hub.make_touch_component(config[CONF_NAME], config[CONF_PAGE_ID],
|
||||
config[CONF_COMPONENT_ID])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
49
esphome/components/binary_sensor/pn532.py
Normal file
49
esphome/components/binary_sensor/pn532.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.pn532 import PN532Component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_UID
|
||||
from esphome.core import HexInt
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['pn532']
|
||||
|
||||
CONF_PN532_ID = 'pn532_id'
|
||||
|
||||
|
||||
def validate_uid(value):
|
||||
value = cv.string_strict(value)
|
||||
for x in value.split('-'):
|
||||
if len(x) != 2:
|
||||
raise vol.Invalid("Each part (separated by '-') of the UID must be two characters "
|
||||
"long.")
|
||||
try:
|
||||
x = int(x, 16)
|
||||
except ValueError:
|
||||
raise vol.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.")
|
||||
if x < 0 or x > 255:
|
||||
raise vol.Invalid("Valid values for UID parts (separated by '-') are 00 to FF")
|
||||
return value
|
||||
|
||||
|
||||
PN532BinarySensor = binary_sensor.binary_sensor_ns.class_('PN532BinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(PN532BinarySensor),
|
||||
vol.Required(CONF_UID): validate_uid,
|
||||
cv.GenerateID(CONF_PN532_ID): cv.use_variable_id(PN532Component)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_PN532_ID]):
|
||||
yield
|
||||
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')]
|
||||
rhs = hub.make_tag(config[CONF_NAME], addr)
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
30
esphome/components/binary_sensor/rdm6300.py
Normal file
30
esphome/components/binary_sensor/rdm6300.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, rdm6300
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_UID
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['rdm6300']
|
||||
|
||||
CONF_RDM6300_ID = 'rdm6300_id'
|
||||
|
||||
RDM6300BinarySensor = binary_sensor.binary_sensor_ns.class_('RDM6300BinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(RDM6300BinarySensor),
|
||||
vol.Required(CONF_UID): cv.uint32_t,
|
||||
cv.GenerateID(CONF_RDM6300_ID): cv.use_variable_id(rdm6300.RDM6300Component)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_RDM6300_ID]):
|
||||
yield
|
||||
rhs = hub.make_card(config[CONF_NAME], config[CONF_UID])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
144
esphome/components/binary_sensor/remote_receiver.py
Normal file
144
esphome/components/binary_sensor/remote_receiver.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.remote_receiver import RemoteReceiverComponent, remote_ns
|
||||
from esphome.components.remote_transmitter import RC_SWITCH_RAW_SCHEMA, \
|
||||
RC_SWITCH_TYPE_A_SCHEMA, RC_SWITCH_TYPE_B_SCHEMA, RC_SWITCH_TYPE_C_SCHEMA, \
|
||||
RC_SWITCH_TYPE_D_SCHEMA, binary_code, build_rc_switch_protocol
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_CHANNEL, CONF_CODE, CONF_COMMAND, CONF_DATA, \
|
||||
CONF_DEVICE, CONF_FAMILY, CONF_GROUP, CONF_ID, CONF_JVC, CONF_LG, CONF_NAME, CONF_NBITS, \
|
||||
CONF_NEC, CONF_PANASONIC, CONF_PROTOCOL, CONF_RAW, CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, \
|
||||
CONF_RC_SWITCH_TYPE_B, CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D, CONF_SAMSUNG, CONF_SONY, \
|
||||
CONF_STATE
|
||||
from esphome.cpp_generator import Pvariable, get_variable, progmem_array
|
||||
from esphome.cpp_types import int32
|
||||
|
||||
DEPENDENCIES = ['remote_receiver']
|
||||
|
||||
REMOTE_KEYS = [CONF_JVC, CONF_NEC, CONF_LG, CONF_SONY, CONF_PANASONIC, CONF_SAMSUNG, CONF_RAW,
|
||||
CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, CONF_RC_SWITCH_TYPE_B,
|
||||
CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D]
|
||||
|
||||
CONF_REMOTE_RECEIVER_ID = 'remote_receiver_id'
|
||||
CONF_RECEIVER_ID = 'receiver_id'
|
||||
|
||||
RemoteReceiver = remote_ns.class_('RemoteReceiver', binary_sensor.BinarySensor)
|
||||
JVCReceiver = remote_ns.class_('JVCReceiver', RemoteReceiver)
|
||||
LGReceiver = remote_ns.class_('LGReceiver', RemoteReceiver)
|
||||
NECReceiver = remote_ns.class_('NECReceiver', RemoteReceiver)
|
||||
PanasonicReceiver = remote_ns.class_('PanasonicReceiver', RemoteReceiver)
|
||||
RawReceiver = remote_ns.class_('RawReceiver', RemoteReceiver)
|
||||
SamsungReceiver = remote_ns.class_('SamsungReceiver', RemoteReceiver)
|
||||
SonyReceiver = remote_ns.class_('SonyReceiver', RemoteReceiver)
|
||||
RCSwitchRawReceiver = remote_ns.class_('RCSwitchRawReceiver', RemoteReceiver)
|
||||
RCSwitchTypeAReceiver = remote_ns.class_('RCSwitchTypeAReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeBReceiver = remote_ns.class_('RCSwitchTypeBReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeCReceiver = remote_ns.class_('RCSwitchTypeCReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeDReceiver = remote_ns.class_('RCSwitchTypeDReceiver', RCSwitchRawReceiver)
|
||||
|
||||
|
||||
def validate_raw(value):
|
||||
if isinstance(value, dict):
|
||||
return vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(int32),
|
||||
vol.Required(CONF_DATA): [vol.Any(vol.Coerce(int), cv.time_period_microseconds)],
|
||||
})(value)
|
||||
return validate_raw({
|
||||
CONF_DATA: value
|
||||
})
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(RemoteReceiver),
|
||||
vol.Optional(CONF_JVC): vol.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_LG): vol.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
vol.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True),
|
||||
}),
|
||||
vol.Optional(CONF_NEC): vol.Schema({
|
||||
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
vol.Required(CONF_COMMAND): cv.hex_uint16_t,
|
||||
}),
|
||||
vol.Optional(CONF_SAMSUNG): vol.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_SONY): vol.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
vol.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True),
|
||||
}),
|
||||
vol.Optional(CONF_PANASONIC): vol.Schema({
|
||||
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
vol.Required(CONF_COMMAND): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_RAW): validate_raw,
|
||||
vol.Optional(CONF_RC_SWITCH_RAW): RC_SWITCH_RAW_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_A): RC_SWITCH_TYPE_A_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_B): RC_SWITCH_TYPE_B_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_C): RC_SWITCH_TYPE_C_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_D): RC_SWITCH_TYPE_D_SCHEMA,
|
||||
|
||||
cv.GenerateID(CONF_REMOTE_RECEIVER_ID): cv.use_variable_id(RemoteReceiverComponent),
|
||||
cv.GenerateID(CONF_RECEIVER_ID): cv.declare_variable_id(RemoteReceiver),
|
||||
}), cv.has_exactly_one_key(*REMOTE_KEYS))
|
||||
|
||||
|
||||
def receiver_base(full_config):
|
||||
name = full_config[CONF_NAME]
|
||||
key, config = next((k, v) for k, v in full_config.items() if k in REMOTE_KEYS)
|
||||
if key == CONF_JVC:
|
||||
return JVCReceiver.new(name, config[CONF_DATA])
|
||||
if key == CONF_LG:
|
||||
return LGReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
|
||||
if key == CONF_NEC:
|
||||
return NECReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
|
||||
if key == CONF_PANASONIC:
|
||||
return PanasonicReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
|
||||
if key == CONF_SAMSUNG:
|
||||
return SamsungReceiver.new(name, config[CONF_DATA])
|
||||
if key == CONF_SONY:
|
||||
return SonyReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
|
||||
if key == CONF_RAW:
|
||||
arr = progmem_array(config[CONF_ID], config[CONF_DATA])
|
||||
return RawReceiver.new(name, arr, len(config[CONF_DATA]))
|
||||
if key == CONF_RC_SWITCH_RAW:
|
||||
return RCSwitchRawReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
binary_code(config[CONF_CODE]), len(config[CONF_CODE]))
|
||||
if key == CONF_RC_SWITCH_TYPE_A:
|
||||
return RCSwitchTypeAReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
binary_code(config[CONF_GROUP]),
|
||||
binary_code(config[CONF_DEVICE]),
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_B:
|
||||
return RCSwitchTypeBReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
config[CONF_ADDRESS], config[CONF_CHANNEL],
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_C:
|
||||
return RCSwitchTypeCReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
ord(config[CONF_FAMILY][0]) - ord('a'),
|
||||
config[CONF_GROUP], config[CONF_DEVICE],
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_D:
|
||||
return RCSwitchTypeDReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
ord(config[CONF_GROUP][0]) - ord('a'),
|
||||
config[CONF_DEVICE], config[CONF_STATE])
|
||||
|
||||
raise NotImplementedError("Unknown receiver type {}".format(config))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for remote in get_variable(config[CONF_REMOTE_RECEIVER_ID]):
|
||||
yield
|
||||
rhs = receiver_base(config)
|
||||
receiver = Pvariable(config[CONF_RECEIVER_ID], rhs)
|
||||
|
||||
binary_sensor.register_binary_sensor(remote.add_decoder(receiver), config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_REMOTE_RECEIVER'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
28
esphome/components/binary_sensor/status.py
Normal file
28
esphome/components/binary_sensor/status.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
StatusBinarySensor = binary_sensor.binary_sensor_ns.class_('StatusBinarySensor',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(StatusBinarySensor),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_status_binary_sensor(config[CONF_NAME])
|
||||
status = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(status, config)
|
||||
setup_component(status, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_STATUS_BINARY_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
59
esphome/components/binary_sensor/template.py
Normal file
59
esphome/components/binary_sensor/template.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_STATE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, Component, bool_, optional
|
||||
|
||||
TemplateBinarySensor = binary_sensor.binary_sensor_ns.class_('TemplateBinarySensor',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
BinarySensorPublishAction = binary_sensor.binary_sensor_ns.class_('BinarySensorPublishAction',
|
||||
Action)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TemplateBinarySensor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_template_binary_sensor(config[CONF_NAME])
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=optional.template(bool_)):
|
||||
yield
|
||||
add(var.set_template(template_))
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_TEMPLATE_BINARY_SENSOR'
|
||||
|
||||
CONF_BINARY_SENSOR_TEMPLATE_PUBLISH = 'binary_sensor.template.publish'
|
||||
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(binary_sensor.BinarySensor),
|
||||
vol.Required(CONF_STATE): cv.templatable(cv.boolean),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_BINARY_SENSOR_TEMPLATE_PUBLISH,
|
||||
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA)
|
||||
def binary_sensor_template_publish_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_binary_sensor_publish_action(template_arg)
|
||||
type = BinarySensorPublishAction.template(arg_type)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for template_ in templatable(config[CONF_STATE], arg_type, bool_):
|
||||
yield None
|
||||
add(action.set_state(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return binary_sensor.core_to_hass_config(data, config)
|
||||
105
esphome/components/cover/__init__.py
Normal file
105
esphome/components/cover/__init__.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable
|
||||
from esphome.cpp_types import Action, Nameable, esphome_ns
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
cover_ns = esphome_ns.namespace('cover')
|
||||
|
||||
Cover = cover_ns.class_('Cover', Nameable)
|
||||
MQTTCoverComponent = cover_ns.class_('MQTTCoverComponent', mqtt.MQTTComponent)
|
||||
|
||||
CoverState = cover_ns.class_('CoverState')
|
||||
COVER_OPEN = cover_ns.COVER_OPEN
|
||||
COVER_CLOSED = cover_ns.COVER_CLOSED
|
||||
|
||||
validate_cover_state = cv.one_of('OPEN', 'CLOSED', upper=True)
|
||||
COVER_STATES = {
|
||||
'OPEN': COVER_OPEN,
|
||||
'CLOSED': COVER_CLOSED,
|
||||
}
|
||||
|
||||
# Actions
|
||||
OpenAction = cover_ns.class_('OpenAction', Action)
|
||||
CloseAction = cover_ns.class_('CloseAction', Action)
|
||||
StopAction = cover_ns.class_('StopAction', Action)
|
||||
|
||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(Cover),
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTCoverComponent),
|
||||
})
|
||||
|
||||
COVER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(COVER_SCHEMA.schema)
|
||||
|
||||
|
||||
def setup_cover_core_(cover_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(cover_var.set_internal(config[CONF_INTERNAL]))
|
||||
setup_mqtt_component(cover_var.Pget_mqtt(), config)
|
||||
|
||||
|
||||
def setup_cover(cover_obj, config):
|
||||
CORE.add_job(setup_cover_core_, cover_obj, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_COVER'
|
||||
|
||||
CONF_COVER_OPEN = 'cover.open'
|
||||
COVER_OPEN_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_OPEN, COVER_OPEN_ACTION_SCHEMA)
|
||||
def cover_open_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_open_action(template_arg)
|
||||
type = OpenAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COVER_CLOSE = 'cover.close'
|
||||
COVER_CLOSE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_CLOSE, COVER_CLOSE_ACTION_SCHEMA)
|
||||
def cover_close_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_close_action(template_arg)
|
||||
type = CloseAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COVER_STOP = 'cover.stop'
|
||||
COVER_STOP_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_STOP, COVER_STOP_ACTION_SCHEMA)
|
||||
def cover_stop_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_stop_action(template_arg)
|
||||
type = StopAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
def core_to_hass_config(data, config):
|
||||
ret = mqtt.build_hass_config(data, 'cover', config, include_state=True, include_command=True)
|
||||
if ret is None:
|
||||
return None
|
||||
return ret
|
||||
88
esphome/components/cover/template.py
Normal file
88
esphome/components/cover/template.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
from esphome.components import cover
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_ID, CONF_LAMBDA, CONF_NAME, \
|
||||
CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_STATE, CONF_STOP_ACTION
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, NoArg, optional
|
||||
from esphome.py_compat import string_types
|
||||
|
||||
TemplateCover = cover.cover_ns.class_('TemplateCover', cover.Cover)
|
||||
CoverPublishAction = cover.cover_ns.class_('CoverPublishAction', Action)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TemplateCover),
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_ASSUMED_STATE): cv.boolean,
|
||||
vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
vol.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_template_cover(config[CONF_NAME])
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
cover.setup_cover(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=optional.template(cover.CoverState)):
|
||||
yield
|
||||
add(var.set_state_lambda(template_))
|
||||
if CONF_OPEN_ACTION in config:
|
||||
automation.build_automation(var.get_open_trigger(), NoArg,
|
||||
config[CONF_OPEN_ACTION])
|
||||
if CONF_CLOSE_ACTION in config:
|
||||
automation.build_automation(var.get_close_trigger(), NoArg,
|
||||
config[CONF_CLOSE_ACTION])
|
||||
if CONF_STOP_ACTION in config:
|
||||
automation.build_automation(var.get_stop_trigger(), NoArg,
|
||||
config[CONF_STOP_ACTION])
|
||||
if CONF_OPTIMISTIC in config:
|
||||
add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_ASSUMED_STATE in config:
|
||||
add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_TEMPLATE_COVER'
|
||||
|
||||
CONF_COVER_TEMPLATE_PUBLISH = 'cover.template.publish'
|
||||
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(cover.Cover),
|
||||
vol.Required(CONF_STATE): cv.templatable(cover.validate_cover_state),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_TEMPLATE_PUBLISH,
|
||||
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA)
|
||||
def cover_template_publish_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_cover_publish_action(template_arg)
|
||||
type = CoverPublishAction.template(arg_type)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
state = config[CONF_STATE]
|
||||
if isinstance(state, string_types):
|
||||
template_ = cover.COVER_STATES[state]
|
||||
else:
|
||||
for template_ in templatable(state, arg_type, cover.CoverState):
|
||||
yield None
|
||||
add(action.set_state(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
ret = cover.core_to_hass_config(data, config)
|
||||
if ret is None:
|
||||
return None
|
||||
if CONF_OPTIMISTIC in config:
|
||||
ret['optimistic'] = config[CONF_OPTIMISTIC]
|
||||
return ret
|
||||
33
esphome/components/custom_component.py
Normal file
33
esphome/components/custom_component.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA
|
||||
from esphome.cpp_generator import Pvariable, process_lambda, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Component, ComponentPtr, esphome_ns, std_vector
|
||||
|
||||
CustomComponentConstructor = esphome_ns.class_('CustomComponentConstructor')
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(CustomComponentConstructor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_COMPONENTS): cv.ensure_list(vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(Component)
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=std_vector.template(ComponentPtr)):
|
||||
yield
|
||||
|
||||
rhs = CustomComponentConstructor(template_)
|
||||
custom = variable(config[CONF_ID], rhs)
|
||||
for i, comp_config in enumerate(config.get(CONF_COMPONENTS, [])):
|
||||
comp = Pvariable(comp_config[CONF_ID], custom.get_component(i))
|
||||
setup_component(comp, comp_config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_CUSTOM_COMPONENT'
|
||||
27
esphome/components/dallas.py
Normal file
27
esphome/components/dallas.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PIN, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DallasComponent = sensor.sensor_ns.class_('DallasComponent', PollingComponent)
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(DallasComponent),
|
||||
vol.Required(CONF_PIN): pins.input_pullup_pin,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_dallas_component(config[CONF_PIN], config.get(CONF_UPDATE_INTERVAL))
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
setup_component(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DALLAS_SENSOR'
|
||||
15
esphome/components/debug.py
Normal file
15
esphome/components/debug.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.cpp_generator import add
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DEPENDENCIES = ['logger']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
add(App.make_debug_component())
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DEBUG_COMPONENT'
|
||||
118
esphome/components/deep_sleep.py
Normal file
118
esphome/components/deep_sleep.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv, pins
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \
|
||||
CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN
|
||||
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||
from esphome.cpp_types import Action, App, Component, esphome_ns, global_ns
|
||||
|
||||
|
||||
def validate_pin_number(value):
|
||||
valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 39]
|
||||
if value[CONF_NUMBER] not in valid_pins:
|
||||
raise vol.Invalid(u"Only pins {} support wakeup"
|
||||
u"".format(', '.join(str(x) for x in valid_pins)))
|
||||
return value
|
||||
|
||||
|
||||
DeepSleepComponent = esphome_ns.class_('DeepSleepComponent', Component)
|
||||
EnterDeepSleepAction = esphome_ns.class_('EnterDeepSleepAction', Action)
|
||||
PreventDeepSleepAction = esphome_ns.class_('PreventDeepSleepAction', Action)
|
||||
|
||||
WakeupPinMode = esphome_ns.enum('WakeupPinMode')
|
||||
WAKEUP_PIN_MODES = {
|
||||
'IGNORE': WakeupPinMode.WAKEUP_PIN_MODE_IGNORE,
|
||||
'KEEP_AWAKE': WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE,
|
||||
'INVERT_WAKEUP': WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
||||
}
|
||||
|
||||
esp_sleep_ext1_wakeup_mode_t = global_ns.enum('esp_sleep_ext1_wakeup_mode_t')
|
||||
Ext1Wakeup = esphome_ns.struct('Ext1Wakeup')
|
||||
EXT1_WAKEUP_MODES = {
|
||||
'ALL_LOW': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
||||
'ANY_HIGH': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
||||
}
|
||||
|
||||
CONF_WAKEUP_PIN_MODE = 'wakeup_pin_mode'
|
||||
CONF_ESP32_EXT1_WAKEUP = 'esp32_ext1_wakeup'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(DeepSleepComponent),
|
||||
vol.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_WAKEUP_PIN): vol.All(cv.only_on_esp32, pins.internal_gpio_input_pin_schema,
|
||||
validate_pin_number),
|
||||
vol.Optional(CONF_WAKEUP_PIN_MODE): vol.All(cv.only_on_esp32,
|
||||
cv.one_of(*WAKEUP_PIN_MODES), upper=True),
|
||||
vol.Optional(CONF_ESP32_EXT1_WAKEUP): vol.All(cv.only_on_esp32, vol.Schema({
|
||||
vol.Required(CONF_PINS): cv.ensure_list(pins.shorthand_input_pin, validate_pin_number),
|
||||
vol.Required(CONF_MODE): cv.one_of(*EXT1_WAKEUP_MODES, upper=True),
|
||||
})),
|
||||
vol.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds,
|
||||
|
||||
vol.Optional(CONF_RUN_CYCLES): cv.invalid("The run_cycles option has been removed in 1.11.0 as "
|
||||
"it was essentially the same as a run_duration of 0s."
|
||||
"Please use run_duration now.")
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_deep_sleep_component()
|
||||
deep_sleep = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_SLEEP_DURATION in config:
|
||||
add(deep_sleep.set_sleep_duration(config[CONF_SLEEP_DURATION]))
|
||||
if CONF_WAKEUP_PIN in config:
|
||||
for pin in gpio_input_pin_expression(config[CONF_WAKEUP_PIN]):
|
||||
yield
|
||||
add(deep_sleep.set_wakeup_pin(pin))
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
add(deep_sleep.set_wakeup_pin_mode(WAKEUP_PIN_MODES[config[CONF_WAKEUP_PIN_MODE]]))
|
||||
if CONF_RUN_DURATION in config:
|
||||
add(deep_sleep.set_run_duration(config[CONF_RUN_DURATION]))
|
||||
|
||||
if CONF_ESP32_EXT1_WAKEUP in config:
|
||||
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
||||
mask = 0
|
||||
for pin in conf[CONF_PINS]:
|
||||
mask |= 1 << pin[CONF_NUMBER]
|
||||
struct = StructInitializer(
|
||||
Ext1Wakeup,
|
||||
('mask', mask),
|
||||
('wakeup_mode', EXT1_WAKEUP_MODES[conf[CONF_MODE]])
|
||||
)
|
||||
add(deep_sleep.set_ext1_wakeup(struct))
|
||||
|
||||
setup_component(deep_sleep, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DEEP_SLEEP'
|
||||
|
||||
CONF_DEEP_SLEEP_ENTER = 'deep_sleep.enter'
|
||||
DEEP_SLEEP_ENTER_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(DeepSleepComponent),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_ENTER, DEEP_SLEEP_ENTER_ACTION_SCHEMA)
|
||||
def deep_sleep_enter_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_enter_deep_sleep_action(template_arg)
|
||||
type = EnterDeepSleepAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_DEEP_SLEEP_PREVENT = 'deep_sleep.prevent'
|
||||
DEEP_SLEEP_PREVENT_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(DeepSleepComponent),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_PREVENT, DEEP_SLEEP_PREVENT_ACTION_SCHEMA)
|
||||
def deep_sleep_prevent_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_prevent_deep_sleep_action(template_arg)
|
||||
type = PreventDeepSleepAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
58
esphome/components/display/__init__.py
Normal file
58
esphome/components/display/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# coding=utf-8
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_LAMBDA, CONF_ROTATION, CONF_UPDATE_INTERVAL
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import add
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
display_ns = esphome_ns.namespace('display')
|
||||
DisplayBuffer = display_ns.class_('DisplayBuffer')
|
||||
DisplayBufferRef = DisplayBuffer.operator('ref')
|
||||
|
||||
DISPLAY_ROTATIONS = {
|
||||
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
|
||||
90: display_ns.DISPLAY_ROTATION_90_DEGREES,
|
||||
180: display_ns.DISPLAY_ROTATION_180_DEGREES,
|
||||
270: display_ns.DISPLAY_ROTATION_270_DEGREES,
|
||||
}
|
||||
|
||||
|
||||
def validate_rotation(value):
|
||||
value = cv.string(value)
|
||||
if value.endswith(u"°"):
|
||||
value = value[:-1]
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
raise vol.Invalid(u"Expected integer for rotation")
|
||||
return cv.one_of(*DISPLAY_ROTATIONS)(value)
|
||||
|
||||
|
||||
BASIC_DISPLAY_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
})
|
||||
|
||||
FULL_DISPLAY_PLATFORM_SCHEMA = BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_ROTATION): validate_rotation,
|
||||
})
|
||||
|
||||
|
||||
def setup_display_core_(display_var, config):
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(display_var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
if CONF_ROTATION in config:
|
||||
add(display_var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
|
||||
|
||||
|
||||
def setup_display(display_var, config):
|
||||
CORE.add_job(setup_display_core_, display_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DISPLAY'
|
||||
76
esphome/components/display/lcd_gpio.py
Normal file
76
esphome/components/display/lcd_gpio.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import display
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA_PINS, CONF_DIMENSIONS, CONF_ENABLE_PIN, CONF_ID, \
|
||||
CONF_LAMBDA, CONF_RS_PIN, CONF_RW_PIN
|
||||
from esphome.cpp_generator import Pvariable, add, process_lambda
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, void
|
||||
|
||||
LCDDisplay = display.display_ns.class_('LCDDisplay', PollingComponent)
|
||||
LCDDisplayRef = LCDDisplay.operator('ref')
|
||||
GPIOLCDDisplay = display.display_ns.class_('GPIOLCDDisplay', LCDDisplay)
|
||||
|
||||
|
||||
def validate_lcd_dimensions(value):
|
||||
value = cv.dimensions(value)
|
||||
if value[0] > 0x40:
|
||||
raise vol.Invalid("LCD displays can't have more than 64 columns")
|
||||
if value[1] > 4:
|
||||
raise vol.Invalid("LCD displays can't have more than 4 rows")
|
||||
return value
|
||||
|
||||
|
||||
def validate_pin_length(value):
|
||||
if len(value) != 4 and len(value) != 8:
|
||||
raise vol.Invalid("LCD Displays can either operate in 4-pin or 8-pin mode,"
|
||||
"not {}-pin mode".format(len(value)))
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(GPIOLCDDisplay),
|
||||
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
|
||||
|
||||
vol.Required(CONF_DATA_PINS): vol.All([pins.gpio_output_pin_schema], validate_pin_length),
|
||||
vol.Required(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_RS_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_RW_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_gpio_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
|
||||
lcd = Pvariable(config[CONF_ID], rhs)
|
||||
pins_ = []
|
||||
for conf in config[CONF_DATA_PINS]:
|
||||
for pin in gpio_output_pin_expression(conf):
|
||||
yield
|
||||
pins_.append(pin)
|
||||
add(lcd.set_data_pins(*pins_))
|
||||
for enable in gpio_output_pin_expression(config[CONF_ENABLE_PIN]):
|
||||
yield
|
||||
add(lcd.set_enable_pin(enable))
|
||||
|
||||
for rs in gpio_output_pin_expression(config[CONF_RS_PIN]):
|
||||
yield
|
||||
add(lcd.set_rs_pin(rs))
|
||||
|
||||
if CONF_RW_PIN in config:
|
||||
for rw in gpio_output_pin_expression(config[CONF_RW_PIN]):
|
||||
yield
|
||||
add(lcd.set_rw_pin(rw))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')],
|
||||
return_type=void):
|
||||
yield
|
||||
add(lcd.set_writer(lambda_))
|
||||
|
||||
display.setup_display(lcd, config)
|
||||
setup_component(lcd, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_LCD_DISPLAY'
|
||||
40
esphome/components/display/lcd_pcf8574.py
Normal file
40
esphome/components/display/lcd_pcf8574.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import display, i2c
|
||||
from esphome.components.display.lcd_gpio import LCDDisplay, LCDDisplayRef, \
|
||||
validate_lcd_dimensions
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_DIMENSIONS, CONF_ID, CONF_LAMBDA
|
||||
from esphome.cpp_generator import Pvariable, add, process_lambda
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, void
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
PCF8574LCDDisplay = display.display_ns.class_('PCF8574LCDDisplay', LCDDisplay, i2c.I2CDevice)
|
||||
|
||||
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(PCF8574LCDDisplay),
|
||||
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_pcf8574_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
|
||||
lcd = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_ADDRESS in config:
|
||||
add(lcd.set_address(config[CONF_ADDRESS]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')],
|
||||
return_type=void):
|
||||
yield
|
||||
add(lcd.set_writer(lambda_))
|
||||
|
||||
display.setup_display(lcd, config)
|
||||
setup_component(lcd, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = ['-DUSE_LCD_DISPLAY', '-DUSE_LCD_DISPLAY_PCF8574']
|
||||
51
esphome/components/display/max7219.py
Normal file
51
esphome/components/display/max7219.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.spi import SPIComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CS_PIN, CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS, \
|
||||
CONF_SPI_ID
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, void
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
MAX7219Component = display.display_ns.class_('MAX7219Component', PollingComponent, spi.SPIDevice)
|
||||
MAX7219ComponentRef = MAX7219Component.operator('ref')
|
||||
|
||||
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(MAX7219Component),
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
|
||||
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
|
||||
vol.Optional(CONF_NUM_CHIPS): vol.All(cv.uint8_t, vol.Range(min=1)),
|
||||
vol.Optional(CONF_INTENSITY): vol.All(cv.uint8_t, vol.Range(min=0, max=15)),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for spi_ in get_variable(config[CONF_SPI_ID]):
|
||||
yield
|
||||
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
|
||||
yield
|
||||
rhs = App.make_max7219(spi_, cs)
|
||||
max7219 = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_NUM_CHIPS in config:
|
||||
add(max7219.set_num_chips(config[CONF_NUM_CHIPS]))
|
||||
if CONF_INTENSITY in config:
|
||||
add(max7219.set_intensity(config[CONF_INTENSITY]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')],
|
||||
return_type=void):
|
||||
yield
|
||||
add(max7219.set_writer(lambda_))
|
||||
|
||||
display.setup_display(max7219, config)
|
||||
setup_component(max7219, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_MAX7219'
|
||||
36
esphome/components/display/nextion.py
Normal file
36
esphome/components/display/nextion.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from esphome.components import display, uart
|
||||
from esphome.components.uart import UARTComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_UART_ID
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, void
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
Nextion = display.display_ns.class_('Nextion', PollingComponent, uart.UARTDevice)
|
||||
NextionRef = Nextion.operator('ref')
|
||||
|
||||
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(Nextion),
|
||||
cv.GenerateID(CONF_UART_ID): cv.use_variable_id(UARTComponent),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for uart_ in get_variable(config[CONF_UART_ID]):
|
||||
yield
|
||||
rhs = App.make_nextion(uart_)
|
||||
nextion = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(NextionRef, 'it')],
|
||||
return_type=void):
|
||||
yield
|
||||
add(nextion.set_writer(lambda_))
|
||||
|
||||
display.setup_display(nextion, config)
|
||||
setup_component(nextion, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_NEXTION'
|
||||
48
esphome/components/display/ssd1306_i2c.py
Normal file
48
esphome/components/display/ssd1306_i2c.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import display
|
||||
from esphome.components.display import ssd1306_spi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_EXTERNAL_VCC, CONF_ID, CONF_LAMBDA, CONF_MODEL, \
|
||||
CONF_RESET_PIN
|
||||
from esphome.cpp_generator import Pvariable, add, process_lambda
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, void
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
I2CSSD1306 = display.display_ns.class_('I2CSSD1306', ssd1306_spi.SSD1306)
|
||||
|
||||
PLATFORM_SCHEMA = display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(I2CSSD1306),
|
||||
vol.Required(CONF_MODEL): ssd1306_spi.SSD1306_MODEL,
|
||||
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
ssd = Pvariable(config[CONF_ID], App.make_i2c_ssd1306())
|
||||
add(ssd.set_model(ssd1306_spi.MODELS[config[CONF_MODEL]]))
|
||||
|
||||
if CONF_RESET_PIN in config:
|
||||
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
|
||||
yield
|
||||
add(ssd.set_reset_pin(reset))
|
||||
if CONF_EXTERNAL_VCC in config:
|
||||
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
|
||||
if CONF_ADDRESS in config:
|
||||
add(ssd.set_address(config[CONF_ADDRESS]))
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA],
|
||||
[(display.DisplayBufferRef, 'it')], return_type=void):
|
||||
yield
|
||||
add(ssd.set_writer(lambda_))
|
||||
|
||||
display.setup_display(ssd, config)
|
||||
setup_component(ssd, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_SSD1306'
|
||||
71
esphome/components/display/ssd1306_spi.py
Normal file
71
esphome/components/display/ssd1306_spi.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.spi import SPIComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CS_PIN, CONF_DC_PIN, CONF_EXTERNAL_VCC, CONF_ID, CONF_LAMBDA, \
|
||||
CONF_MODEL, CONF_RESET_PIN, CONF_SPI_ID
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, void
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
SSD1306 = display.display_ns.class_('SSD1306', PollingComponent, display.DisplayBuffer)
|
||||
SPISSD1306 = display.display_ns.class_('SPISSD1306', SSD1306, spi.SPIDevice)
|
||||
SSD1306Model = display.display_ns.enum('SSD1306Model')
|
||||
|
||||
MODELS = {
|
||||
'SSD1306_128X32': SSD1306Model.SSD1306_MODEL_128_32,
|
||||
'SSD1306_128X64': SSD1306Model.SSD1306_MODEL_128_64,
|
||||
'SSD1306_96X16': SSD1306Model.SSD1306_MODEL_96_16,
|
||||
'SSD1306_64X48': SSD1306Model.SSD1306_MODEL_64_48,
|
||||
'SH1106_128X32': SSD1306Model.SH1106_MODEL_128_32,
|
||||
'SH1106_128X64': SSD1306Model.SH1106_MODEL_128_64,
|
||||
'SH1106_96X16': SSD1306Model.SH1106_MODEL_96_16,
|
||||
'SH1106_64X48': SSD1306Model.SH1106_MODEL_64_48,
|
||||
}
|
||||
|
||||
SSD1306_MODEL = cv.one_of(*MODELS, upper=True, space="_")
|
||||
|
||||
PLATFORM_SCHEMA = display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(SPISSD1306),
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
|
||||
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_MODEL): SSD1306_MODEL,
|
||||
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for spi_ in get_variable(config[CONF_SPI_ID]):
|
||||
yield
|
||||
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
|
||||
yield
|
||||
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
|
||||
yield
|
||||
|
||||
rhs = App.make_spi_ssd1306(spi_, cs, dc)
|
||||
ssd = Pvariable(config[CONF_ID], rhs)
|
||||
add(ssd.set_model(MODELS[config[CONF_MODEL]]))
|
||||
|
||||
if CONF_RESET_PIN in config:
|
||||
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
|
||||
yield
|
||||
add(ssd.set_reset_pin(reset))
|
||||
if CONF_EXTERNAL_VCC in config:
|
||||
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA],
|
||||
[(display.DisplayBufferRef, 'it')], return_type=void):
|
||||
yield
|
||||
add(ssd.set_writer(lambda_))
|
||||
|
||||
display.setup_display(ssd, config)
|
||||
setup_component(ssd, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_SSD1306'
|
||||
92
esphome/components/display/waveshare_epaper.py
Normal file
92
esphome/components/display/waveshare_epaper.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.spi import SPIComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BUSY_PIN, CONF_CS_PIN, CONF_DC_PIN, CONF_FULL_UPDATE_EVERY, \
|
||||
CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN, CONF_SPI_ID
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, gpio_output_pin_expression, \
|
||||
setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, void
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
WaveshareEPaperTypeA = display.display_ns.WaveshareEPaperTypeA
|
||||
WaveshareEPaper = display.display_ns.class_('WaveshareEPaper',
|
||||
PollingComponent, spi.SPIDevice, display.DisplayBuffer)
|
||||
|
||||
WaveshareEPaperTypeAModel = display.display_ns.enum('WaveshareEPaperTypeAModel')
|
||||
WaveshareEPaperTypeBModel = display.display_ns.enum('WaveshareEPaperTypeBModel')
|
||||
|
||||
MODELS = {
|
||||
'1.54in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN),
|
||||
'2.13in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN),
|
||||
'2.90in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN),
|
||||
'2.70in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_2_7_IN),
|
||||
'4.20in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_4_2_IN),
|
||||
'7.50in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_7_5_IN),
|
||||
}
|
||||
|
||||
|
||||
def validate_full_update_every_only_type_a(value):
|
||||
if CONF_FULL_UPDATE_EVERY not in value:
|
||||
return value
|
||||
if MODELS[value[CONF_MODEL]][0] != 'a':
|
||||
raise vol.Invalid("The 'full_update_every' option is only available for models "
|
||||
"'1.54in', '2.13in' and '2.90in'.")
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(WaveshareEPaper),
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
|
||||
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
|
||||
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
|
||||
vol.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), validate_full_update_every_only_type_a)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for spi_ in get_variable(config[CONF_SPI_ID]):
|
||||
yield
|
||||
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
|
||||
yield
|
||||
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
|
||||
yield
|
||||
|
||||
model_type, model = MODELS[config[CONF_MODEL]]
|
||||
if model_type == 'a':
|
||||
rhs = App.make_waveshare_epaper_type_a(spi_, cs, dc, model)
|
||||
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaperTypeA)
|
||||
elif model_type == 'b':
|
||||
rhs = App.make_waveshare_epaper_type_b(spi_, cs, dc, model)
|
||||
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaper)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
|
||||
return_type=void):
|
||||
yield
|
||||
add(epaper.set_writer(lambda_))
|
||||
if CONF_RESET_PIN in config:
|
||||
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
|
||||
yield
|
||||
add(epaper.set_reset_pin(reset))
|
||||
if CONF_BUSY_PIN in config:
|
||||
for reset in gpio_input_pin_expression(config[CONF_BUSY_PIN]):
|
||||
yield
|
||||
add(epaper.set_busy_pin(reset))
|
||||
if CONF_FULL_UPDATE_EVERY in config:
|
||||
add(epaper.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||
|
||||
display.setup_display(epaper, config)
|
||||
setup_component(epaper, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_WAVESHARE_EPAPER'
|
||||
39
esphome/components/esp32_ble_beacon.py
Normal file
39
esphome/components/esp32_ble_beacon.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_SCAN_INTERVAL, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32
|
||||
from esphome.cpp_generator import Pvariable, RawExpression, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
ESP32BLEBeacon = esphome_ns.class_('ESP32BLEBeacon', Component)
|
||||
|
||||
CONF_MAJOR = 'major'
|
||||
CONF_MINOR = 'minor'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32BLEBeacon),
|
||||
vol.Required(CONF_TYPE): cv.one_of('IBEACON', upper=True),
|
||||
vol.Required(CONF_UUID): cv.uuid,
|
||||
vol.Optional(CONF_MAJOR): cv.uint16_t,
|
||||
vol.Optional(CONF_MINOR): cv.uint16_t,
|
||||
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
uuid = config[CONF_UUID].hex
|
||||
uuid_arr = [RawExpression('0x{}'.format(uuid[i:i + 2])) for i in range(0, len(uuid), 2)]
|
||||
rhs = App.make_esp32_ble_beacon(uuid_arr)
|
||||
ble = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_MAJOR in config:
|
||||
add(ble.set_major(config[CONF_MAJOR]))
|
||||
if CONF_MINOR in config:
|
||||
add(ble.set_minor(config[CONF_MINOR]))
|
||||
|
||||
setup_component(ble, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP32_BLE_BEACON'
|
||||
40
esphome/components/esp32_ble_tracker.py
Normal file
40
esphome/components/esp32_ble_tracker.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ID, CONF_SCAN_INTERVAL, ESP_PLATFORM_ESP32
|
||||
from esphome.core import HexInt
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
CONF_ESP32_BLE_ID = 'esp32_ble_id'
|
||||
ESP32BLETracker = esphome_ns.class_('ESP32BLETracker', Component)
|
||||
XiaomiSensor = esphome_ns.class_('XiaomiSensor', sensor.Sensor)
|
||||
XiaomiDevice = esphome_ns.class_('XiaomiDevice')
|
||||
XIAOMI_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(XiaomiSensor)
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32BLETracker),
|
||||
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_time_period_seconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def make_address_array(address):
|
||||
return [HexInt(i) for i in address.parts]
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_esp32_ble_tracker()
|
||||
ble = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_SCAN_INTERVAL in config:
|
||||
add(ble.set_scan_interval(config[CONF_SCAN_INTERVAL]))
|
||||
|
||||
setup_component(ble, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP32_BLE_TRACKER'
|
||||
89
esphome/components/esp32_touch.py
Normal file
89
esphome/components/esp32_touch.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_HIGH_VOLTAGE_REFERENCE, CONF_ID, CONF_IIR_FILTER, \
|
||||
CONF_LOW_VOLTAGE_REFERENCE, CONF_MEASUREMENT_DURATION, CONF_SETUP_MODE, CONF_SLEEP_DURATION, \
|
||||
CONF_VOLTAGE_ATTENUATION, ESP_PLATFORM_ESP32
|
||||
from esphome.core import TimePeriod
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component, global_ns
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
|
||||
def validate_voltage(values):
|
||||
def validator(value):
|
||||
if isinstance(value, float) and value.is_integer():
|
||||
value = int(value)
|
||||
value = cv.string(value)
|
||||
if not value.endswith('V'):
|
||||
value += 'V'
|
||||
return cv.one_of(*values)(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
LOW_VOLTAGE_REFERENCE = {
|
||||
'0.5V': global_ns.TOUCH_LVOLT_0V5,
|
||||
'0.6V': global_ns.TOUCH_LVOLT_0V6,
|
||||
'0.7V': global_ns.TOUCH_LVOLT_0V7,
|
||||
'0.8V': global_ns.TOUCH_LVOLT_0V8,
|
||||
}
|
||||
HIGH_VOLTAGE_REFERENCE = {
|
||||
'2.4V': global_ns.TOUCH_HVOLT_2V4,
|
||||
'2.5V': global_ns.TOUCH_HVOLT_2V5,
|
||||
'2.6V': global_ns.TOUCH_HVOLT_2V6,
|
||||
'2.7V': global_ns.TOUCH_HVOLT_2V7,
|
||||
}
|
||||
VOLTAGE_ATTENUATION = {
|
||||
'1.5V': global_ns.TOUCH_HVOLT_ATTEN_1V5,
|
||||
'1V': global_ns.TOUCH_HVOLT_ATTEN_1V,
|
||||
'0.5V': global_ns.TOUCH_HVOLT_ATTEN_0V5,
|
||||
'0V': global_ns.TOUCH_HVOLT_ATTEN_0V,
|
||||
}
|
||||
|
||||
ESP32TouchComponent = binary_sensor.binary_sensor_ns.class_('ESP32TouchComponent', Component)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32TouchComponent),
|
||||
vol.Optional(CONF_SETUP_MODE): cv.boolean,
|
||||
vol.Optional(CONF_IIR_FILTER): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_SLEEP_DURATION):
|
||||
vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=436906))),
|
||||
vol.Optional(CONF_MEASUREMENT_DURATION):
|
||||
vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=8192))),
|
||||
vol.Optional(CONF_LOW_VOLTAGE_REFERENCE): validate_voltage(LOW_VOLTAGE_REFERENCE),
|
||||
vol.Optional(CONF_HIGH_VOLTAGE_REFERENCE): validate_voltage(HIGH_VOLTAGE_REFERENCE),
|
||||
vol.Optional(CONF_VOLTAGE_ATTENUATION): validate_voltage(VOLTAGE_ATTENUATION),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_esp32_touch_component()
|
||||
touch = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_SETUP_MODE in config:
|
||||
add(touch.set_setup_mode(config[CONF_SETUP_MODE]))
|
||||
if CONF_IIR_FILTER in config:
|
||||
add(touch.set_iir_filter(config[CONF_IIR_FILTER]))
|
||||
if CONF_SLEEP_DURATION in config:
|
||||
sleep_duration = int(config[CONF_SLEEP_DURATION].total_microseconds * 0.15)
|
||||
add(touch.set_sleep_duration(sleep_duration))
|
||||
if CONF_MEASUREMENT_DURATION in config:
|
||||
measurement_duration = int(config[CONF_MEASUREMENT_DURATION].total_microseconds * 0.125)
|
||||
add(touch.set_measurement_duration(measurement_duration))
|
||||
if CONF_LOW_VOLTAGE_REFERENCE in config:
|
||||
value = LOW_VOLTAGE_REFERENCE[config[CONF_LOW_VOLTAGE_REFERENCE]]
|
||||
add(touch.set_low_voltage_reference(value))
|
||||
if CONF_HIGH_VOLTAGE_REFERENCE in config:
|
||||
value = HIGH_VOLTAGE_REFERENCE[config[CONF_HIGH_VOLTAGE_REFERENCE]]
|
||||
add(touch.set_high_voltage_reference(value))
|
||||
if CONF_VOLTAGE_ATTENUATION in config:
|
||||
value = VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]]
|
||||
add(touch.set_voltage_attenuation(value))
|
||||
|
||||
setup_component(touch, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR'
|
||||
85
esphome/components/ethernet.py
Normal file
85
esphome/components/ethernet.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import wifi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DOMAIN, CONF_ID, CONF_MANUAL_IP, CONF_STATIC_IP, CONF_TYPE, \
|
||||
CONF_USE_ADDRESS, ESP_PLATFORM_ESP32
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression
|
||||
from esphome.cpp_types import App, Component, esphome_ns, global_ns
|
||||
|
||||
CONFLICTS_WITH = ['wifi']
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
CONF_PHY_ADDR = 'phy_addr'
|
||||
CONF_MDC_PIN = 'mdc_pin'
|
||||
CONF_MDIO_PIN = 'mdio_pin'
|
||||
CONF_CLK_MODE = 'clk_mode'
|
||||
CONF_POWER_PIN = 'power_pin'
|
||||
|
||||
EthernetType = esphome_ns.enum('EthernetType')
|
||||
ETHERNET_TYPES = {
|
||||
'LAN8720': EthernetType.ETHERNET_TYPE_LAN8720,
|
||||
'TLK110': EthernetType.ETHERNET_TYPE_TLK110,
|
||||
}
|
||||
|
||||
eth_clock_mode_t = global_ns.enum('eth_clock_mode_t')
|
||||
CLK_MODES = {
|
||||
'GPIO0_IN': eth_clock_mode_t.ETH_CLOCK_GPIO0_IN,
|
||||
'GPIO0_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO0_OUT,
|
||||
'GPIO16_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO16_OUT,
|
||||
'GPIO17_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO17_OUT,
|
||||
}
|
||||
|
||||
EthernetComponent = esphome_ns.class_('EthernetComponent', Component)
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_USE_ADDRESS not in config:
|
||||
if CONF_MANUAL_IP in config:
|
||||
use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP])
|
||||
else:
|
||||
use_address = CORE.name + config[CONF_DOMAIN]
|
||||
config[CONF_USE_ADDRESS] = use_address
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.All(vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(EthernetComponent),
|
||||
vol.Required(CONF_TYPE): cv.one_of(*ETHERNET_TYPES, upper=True),
|
||||
vol.Required(CONF_MDC_PIN): pins.output_pin,
|
||||
vol.Required(CONF_MDIO_PIN): pins.input_output_pin,
|
||||
vol.Optional(CONF_CLK_MODE, default='GPIO0_IN'): cv.one_of(*CLK_MODES, upper=True, space='_'),
|
||||
vol.Optional(CONF_PHY_ADDR, default=0): vol.All(cv.int_, vol.Range(min=0, max=31)),
|
||||
vol.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_MANUAL_IP): wifi.STA_MANUAL_IP_SCHEMA,
|
||||
vol.Optional(CONF_DOMAIN, default='.local'): cv.domain_name,
|
||||
vol.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
|
||||
vol.Optional('hostname'): cv.invalid("The hostname option has been removed in 1.11.0"),
|
||||
}), validate)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.init_ethernet()
|
||||
eth = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
add(eth.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||
add(eth.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||
add(eth.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||
add(eth.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
add(eth.set_clk_mode(CLK_MODES[config[CONF_CLK_MODE]]))
|
||||
add(eth.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_POWER_PIN in config:
|
||||
for pin in gpio_output_pin_expression(config[CONF_POWER_PIN]):
|
||||
yield
|
||||
add(eth.set_power_pin(pin))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
add(eth.set_manual_ip(wifi.manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_ETHERNET'
|
||||
144
esphome/components/fan/__init__.py
Normal file
144
esphome/components/fan/__init__.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_NAME, CONF_OSCILLATING, \
|
||||
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_OUTPUT, CONF_OSCILLATION_STATE_TOPIC, \
|
||||
CONF_SPEED, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
|
||||
from esphome.cpp_types import Action, Application, Component, Nameable, bool_, esphome_ns
|
||||
from esphome.py_compat import string_types
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
fan_ns = esphome_ns.namespace('fan')
|
||||
FanState = fan_ns.class_('FanState', Nameable, Component)
|
||||
MQTTFanComponent = fan_ns.class_('MQTTFanComponent', mqtt.MQTTComponent)
|
||||
MakeFan = Application.struct('MakeFan')
|
||||
|
||||
# Actions
|
||||
TurnOnAction = fan_ns.class_('TurnOnAction', Action)
|
||||
TurnOffAction = fan_ns.class_('TurnOffAction', Action)
|
||||
ToggleAction = fan_ns.class_('ToggleAction', Action)
|
||||
|
||||
FanSpeed = fan_ns.enum('FanSpeed')
|
||||
FAN_SPEED_OFF = FanSpeed.FAN_SPEED_OFF
|
||||
FAN_SPEED_LOW = FanSpeed.FAN_SPEED_LOW
|
||||
FAN_SPEED_MEDIUM = FanSpeed.FAN_SPEED_MEDIUM
|
||||
FAN_SPEED_HIGH = FanSpeed.FAN_SPEED_HIGH
|
||||
|
||||
FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(FanState),
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTFanComponent),
|
||||
vol.Optional(CONF_OSCILLATION_STATE_TOPIC): vol.All(cv.requires_component('mqtt'),
|
||||
cv.publish_topic),
|
||||
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): vol.All(cv.requires_component('mqtt'),
|
||||
cv.subscribe_topic),
|
||||
})
|
||||
|
||||
FAN_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(FAN_SCHEMA.schema)
|
||||
|
||||
FAN_SPEEDS = {
|
||||
'OFF': FAN_SPEED_OFF,
|
||||
'LOW': FAN_SPEED_LOW,
|
||||
'MEDIUM': FAN_SPEED_MEDIUM,
|
||||
'HIGH': FAN_SPEED_HIGH,
|
||||
}
|
||||
|
||||
|
||||
def setup_fan_core_(fan_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(fan_var.set_internal(config[CONF_INTERNAL]))
|
||||
|
||||
mqtt_ = fan_var.Pget_mqtt()
|
||||
if CONF_OSCILLATION_STATE_TOPIC in config:
|
||||
add(mqtt_.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC]))
|
||||
if CONF_OSCILLATION_COMMAND_TOPIC in config:
|
||||
add(mqtt_.set_custom_oscillation_command_topic(config[CONF_OSCILLATION_COMMAND_TOPIC]))
|
||||
if CONF_SPEED_STATE_TOPIC in config:
|
||||
add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC]))
|
||||
if CONF_SPEED_COMMAND_TOPIC in config:
|
||||
add(mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]))
|
||||
setup_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
def setup_fan(fan_obj, config):
|
||||
fan_var = Pvariable(config[CONF_ID], fan_obj, has_side_effects=False)
|
||||
CORE.add_job(setup_fan_core_, fan_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_FAN'
|
||||
|
||||
CONF_FAN_TOGGLE = 'fan.toggle'
|
||||
FAN_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(FanState),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_FAN_TOGGLE, FAN_TOGGLE_ACTION_SCHEMA)
|
||||
def fan_toggle_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_toggle_action(template_arg)
|
||||
type = ToggleAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_FAN_TURN_OFF = 'fan.turn_off'
|
||||
FAN_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(FanState),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_FAN_TURN_OFF, FAN_TURN_OFF_ACTION_SCHEMA)
|
||||
def fan_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_off_action(template_arg)
|
||||
type = TurnOffAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_FAN_TURN_ON = 'fan.turn_on'
|
||||
FAN_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(FanState),
|
||||
vol.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
|
||||
vol.Optional(CONF_SPEED): cv.templatable(cv.one_of(*FAN_SPEEDS, upper=True)),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_FAN_TURN_ON, FAN_TURN_ON_ACTION_SCHEMA)
|
||||
def fan_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_on_action(template_arg)
|
||||
type = TurnOnAction.template(arg_type)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_OSCILLATING in config:
|
||||
for template_ in templatable(config[CONF_OSCILLATING], arg_type, bool_):
|
||||
yield None
|
||||
add(action.set_oscillating(template_))
|
||||
if CONF_SPEED in config:
|
||||
for template_ in templatable(config[CONF_SPEED], arg_type, FanSpeed):
|
||||
yield None
|
||||
if isinstance(template_, string_types):
|
||||
template_ = FAN_SPEEDS[template_]
|
||||
add(action.set_speed(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def core_to_hass_config(data, config):
|
||||
ret = mqtt.build_hass_config(data, 'fan', config, include_state=True, include_command=True)
|
||||
if ret is None:
|
||||
return None
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'oscillation/state')
|
||||
ret['oscillation_state_topic'] = config.get(CONF_OSCILLATION_STATE_TOPIC, default)
|
||||
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'oscillation/command')
|
||||
ret['oscillation_command__topic'] = config.get(CONF_OSCILLATION_COMMAND_TOPIC, default)
|
||||
return ret
|
||||
34
esphome/components/fan/binary.py
Normal file
34
esphome/components/fan/binary.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import fan, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAKE_ID, CONF_NAME, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT
|
||||
from esphome.cpp_generator import add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for output_ in get_variable(config[CONF_OUTPUT]):
|
||||
yield
|
||||
|
||||
rhs = App.make_fan(config[CONF_NAME])
|
||||
fan_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
add(fan_struct.Poutput.set_binary(output_))
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
for oscillation_output in get_variable(config[CONF_OSCILLATION_OUTPUT]):
|
||||
yield
|
||||
add(fan_struct.Poutput.set_oscillation(oscillation_output))
|
||||
|
||||
fan.setup_fan(fan_struct.Pstate, config)
|
||||
setup_component(fan_struct.Poutput, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return fan.core_to_hass_config(data, config)
|
||||
55
esphome/components/fan/speed.py
Normal file
55
esphome/components/fan/speed.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import fan, mqtt, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_HIGH, CONF_LOW, CONF_MAKE_ID, CONF_MEDIUM, CONF_NAME, \
|
||||
CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_SPEED, CONF_SPEED_COMMAND_TOPIC, \
|
||||
CONF_SPEED_STATE_TOPIC
|
||||
from esphome.cpp_generator import add, get_variable, variable
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Optional(CONF_SPEED_STATE_TOPIC): cv.publish_topic,
|
||||
vol.Optional(CONF_SPEED_COMMAND_TOPIC): cv.subscribe_topic,
|
||||
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
vol.Optional(CONF_SPEED): vol.Schema({
|
||||
vol.Required(CONF_LOW): cv.percentage,
|
||||
vol.Required(CONF_MEDIUM): cv.percentage,
|
||||
vol.Required(CONF_HIGH): cv.percentage,
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for output_ in get_variable(config[CONF_OUTPUT]):
|
||||
yield
|
||||
rhs = App.make_fan(config[CONF_NAME])
|
||||
fan_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
if CONF_SPEED in config:
|
||||
speeds = config[CONF_SPEED]
|
||||
add(fan_struct.Poutput.set_speed(output_,
|
||||
speeds[CONF_LOW],
|
||||
speeds[CONF_MEDIUM],
|
||||
speeds[CONF_HIGH]))
|
||||
else:
|
||||
add(fan_struct.Poutput.set_speed(output_))
|
||||
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
for oscillation_output in get_variable(config[CONF_OSCILLATION_OUTPUT]):
|
||||
yield
|
||||
add(fan_struct.Poutput.set_oscillation(oscillation_output))
|
||||
|
||||
fan.setup_fan(fan_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
ret = fan.core_to_hass_config(data, config)
|
||||
if ret is None:
|
||||
return None
|
||||
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'speed/state')
|
||||
ret['speed_state_topic'] = config.get(CONF_SPEED_STATE_TOPIC, default)
|
||||
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'speed/command')
|
||||
ret['speed_command__topic'] = config.get(CONF_SPEED_COMMAND_TOPIC, default)
|
||||
return ret
|
||||
119
esphome/components/font.py
Normal file
119
esphome/components/font.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# coding=utf-8
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.cpp_generator import Pvariable, progmem_array, safe_exp
|
||||
from esphome.cpp_types import App, uint8
|
||||
from esphome.py_compat import sort_by_cmp
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
Font = display.display_ns.class_('Font')
|
||||
Glyph = display.display_ns.class_('Glyph')
|
||||
|
||||
|
||||
def validate_glyphs(value):
|
||||
if isinstance(value, list):
|
||||
value = vol.Schema([cv.string])(value)
|
||||
value = vol.Schema([cv.string])(list(value))
|
||||
|
||||
def comparator(x, y):
|
||||
x_ = x.encode('utf-8')
|
||||
y_ = y.encode('utf-8')
|
||||
|
||||
for c in range(min(len(x_), len(y_))):
|
||||
if x_[c] < y_[c]:
|
||||
return -1
|
||||
if x_[c] > y_[c]:
|
||||
return 1
|
||||
|
||||
if len(x_) < len(y_):
|
||||
return -1
|
||||
if len(x_) > len(y_):
|
||||
return 1
|
||||
raise vol.Invalid(u"Found duplicate glyph {}".format(x))
|
||||
|
||||
sort_by_cmp(value, comparator)
|
||||
return value
|
||||
|
||||
|
||||
def validate_pillow_installed(value):
|
||||
try:
|
||||
import PIL
|
||||
except ImportError:
|
||||
raise vol.Invalid("Please install the pillow python package to use this feature. "
|
||||
"(pip install pillow)")
|
||||
|
||||
if PIL.__version__[0] < '4':
|
||||
raise vol.Invalid("Please update your pillow installation to at least 4.0.x. "
|
||||
"(pip install -U pillow)")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_truetype_file(value):
|
||||
if value.endswith('.zip'): # for Google Fonts downloads
|
||||
raise vol.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
|
||||
u"inside.".format(value))
|
||||
if not value.endswith('.ttf'):
|
||||
raise vol.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
|
||||
u"using the correct format or rename the extension to .ttf")
|
||||
return cv.file_(value)
|
||||
|
||||
|
||||
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
||||
|
||||
FONT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(Font),
|
||||
vol.Required(CONF_FILE): validate_truetype_file,
|
||||
vol.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
||||
vol.Optional(CONF_SIZE, default=20): vol.All(cv.int_, vol.Range(min=1)),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(uint8),
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.All(validate_pillow_installed, FONT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
from PIL import ImageFont
|
||||
|
||||
path = CORE.relative_path(config[CONF_FILE])
|
||||
try:
|
||||
font = ImageFont.truetype(path, config[CONF_SIZE])
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(u"Could not load truetype file {}: {}".format(path, e))
|
||||
|
||||
ascent, descent = font.getmetrics()
|
||||
|
||||
glyph_args = {}
|
||||
data = []
|
||||
for glyph in config[CONF_GLYPHS]:
|
||||
mask = font.getmask(glyph, mode='1')
|
||||
_, (offset_x, offset_y) = font.font.getsize(glyph)
|
||||
width, height = mask.size
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if not mask.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8
|
||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
glyph_args[glyph] = (len(data), offset_x, offset_y, width, height)
|
||||
data += glyph_data
|
||||
|
||||
rhs = safe_exp([HexInt(x) for x in data])
|
||||
prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
glyphs = []
|
||||
for glyph in config[CONF_GLYPHS]:
|
||||
glyphs.append(Glyph(glyph, prog_arr, *glyph_args[glyph]))
|
||||
|
||||
rhs = App.make_font(glyphs, ascent, ascent + descent)
|
||||
Pvariable(config[CONF_ID], rhs)
|
||||
35
esphome/components/globals.py
Normal file
35
esphome/components/globals.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE
|
||||
from esphome.cpp_generator import Pvariable, RawExpression, TemplateArguments, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
GlobalVariableComponent = esphome_ns.class_('GlobalVariableComponent', Component)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(GlobalVariableComponent),
|
||||
vol.Required(CONF_TYPE): cv.string_strict,
|
||||
vol.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||
vol.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
type_ = RawExpression(config[CONF_TYPE])
|
||||
template_args = TemplateArguments(type_)
|
||||
res_type = GlobalVariableComponent.template(template_args)
|
||||
initial_value = None
|
||||
if CONF_INITIAL_VALUE in config:
|
||||
initial_value = RawExpression(config[CONF_INITIAL_VALUE])
|
||||
rhs = App.Pmake_global_variable(template_args, initial_value)
|
||||
glob = Pvariable(config[CONF_ID], rhs, type=res_type)
|
||||
|
||||
if config.get(CONF_RESTORE_VALUE, False):
|
||||
hash_ = hash(config[CONF_ID].id) % 2**32
|
||||
add(glob.set_restore_value(hash_))
|
||||
|
||||
setup_component(glob, config)
|
||||
38
esphome/components/i2c.py
Normal file
38
esphome/components/i2c.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_SCAN, CONF_SCL, \
|
||||
CONF_SDA
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
I2CComponent = esphome_ns.class_('I2CComponent', Component)
|
||||
I2CDevice = pins.I2CDevice
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(I2CComponent),
|
||||
vol.Optional(CONF_SDA, default='SDA'): pins.input_pullup_pin,
|
||||
vol.Optional(CONF_SCL, default='SCL'): pins.input_pullup_pin,
|
||||
vol.Optional(CONF_FREQUENCY): vol.All(cv.frequency, vol.Range(min=0, min_included=False)),
|
||||
vol.Optional(CONF_SCAN): cv.boolean,
|
||||
|
||||
vol.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid("The receive_timeout option has been removed "
|
||||
"because timeouts are already handled by the "
|
||||
"low-level i2c interface.")
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.init_i2c(config[CONF_SDA], config[CONF_SCL], config.get(CONF_SCAN))
|
||||
i2c = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_FREQUENCY in config:
|
||||
add(i2c.set_frequency(config[CONF_FREQUENCY]))
|
||||
|
||||
setup_component(i2c, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_I2C'
|
||||
|
||||
LIB_DEPS = 'Wire'
|
||||
63
esphome/components/image.py
Normal file
63
esphome/components/image.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# coding=utf-8
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display, font
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.cpp_generator import Pvariable, progmem_array, safe_exp
|
||||
from esphome.cpp_types import App, uint8
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
Image_ = display.display_ns.class_('Image')
|
||||
|
||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
||||
|
||||
IMAGE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(Image_),
|
||||
vol.Required(CONF_FILE): cv.file_,
|
||||
vol.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(uint8),
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.All(font.validate_pillow_installed, IMAGE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_path(config[CONF_FILE])
|
||||
try:
|
||||
image = Image.open(path)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(u"Could not load image file {}: {}".format(path, e))
|
||||
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
|
||||
image = image.convert('1', dither=Image.NONE)
|
||||
width, height = image.size
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using the resize "
|
||||
"parameter")
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if image.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
rhs = safe_exp([HexInt(x) for x in data])
|
||||
prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
rhs = App.make_image(prog_arr, width, height)
|
||||
Pvariable(config[CONF_ID], rhs)
|
||||
24
esphome/components/interval.py
Normal file
24
esphome/components/interval.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, NoArg, PollingComponent, Trigger, esphome_ns
|
||||
|
||||
IntervalTrigger = esphome_ns.class_('IntervalTrigger', Trigger.template(NoArg), PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = automation.validate_automation(vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(IntervalTrigger),
|
||||
vol.Required(CONF_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for conf in config:
|
||||
rhs = App.register_component(IntervalTrigger.new(conf[CONF_INTERVAL]))
|
||||
trigger = Pvariable(conf[CONF_ID], rhs)
|
||||
setup_component(trigger, conf)
|
||||
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
520
esphome/components/light/__init__.py
Normal file
520
esphome/components/light/__init__.py
Normal file
@@ -0,0 +1,520 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ALPHA, CONF_BLUE, CONF_BRIGHTNESS, CONF_COLORS, \
|
||||
CONF_COLOR_TEMPERATURE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_DURATION, CONF_EFFECT, \
|
||||
CONF_EFFECTS, CONF_EFFECT_ID, CONF_FLASH_LENGTH, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_ID, \
|
||||
CONF_INTERNAL, CONF_LAMBDA, CONF_MQTT_ID, CONF_NAME, CONF_NUM_LEDS, CONF_RANDOM, CONF_RED, \
|
||||
CONF_SPEED, CONF_STATE, CONF_TRANSITION_LENGTH, CONF_UPDATE_INTERVAL, CONF_WHITE, CONF_WIDTH
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda, \
|
||||
templatable
|
||||
from esphome.cpp_types import Action, Application, Component, Nameable, esphome_ns, float_, \
|
||||
std_string, uint32, void
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
# Base
|
||||
light_ns = esphome_ns.namespace('light')
|
||||
LightState = light_ns.class_('LightState', Nameable, Component)
|
||||
MakeLight = Application.struct('MakeLight')
|
||||
LightOutput = light_ns.class_('LightOutput')
|
||||
AddressableLight = light_ns.class_('AddressableLight')
|
||||
AddressableLightRef = AddressableLight.operator('ref')
|
||||
|
||||
# Actions
|
||||
ToggleAction = light_ns.class_('ToggleAction', Action)
|
||||
TurnOffAction = light_ns.class_('TurnOffAction', Action)
|
||||
TurnOnAction = light_ns.class_('TurnOnAction', Action)
|
||||
|
||||
LightColorValues = light_ns.class_('LightColorValues')
|
||||
|
||||
MQTTJSONLightComponent = light_ns.class_('MQTTJSONLightComponent', mqtt.MQTTComponent)
|
||||
|
||||
# Effects
|
||||
LightEffect = light_ns.class_('LightEffect')
|
||||
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
|
||||
LambdaLightEffect = light_ns.class_('LambdaLightEffect', LightEffect)
|
||||
StrobeLightEffect = light_ns.class_('StrobeLightEffect', LightEffect)
|
||||
StrobeLightEffectColor = light_ns.class_('StrobeLightEffectColor', LightEffect)
|
||||
FlickerLightEffect = light_ns.class_('FlickerLightEffect', LightEffect)
|
||||
AddressableLightEffect = light_ns.class_('AddressableLightEffect', LightEffect)
|
||||
AddressableLambdaLightEffect = light_ns.class_('AddressableLambdaLightEffect',
|
||||
AddressableLightEffect)
|
||||
AddressableRainbowLightEffect = light_ns.class_('AddressableRainbowLightEffect',
|
||||
AddressableLightEffect)
|
||||
AddressableColorWipeEffect = light_ns.class_('AddressableColorWipeEffect', AddressableLightEffect)
|
||||
AddressableColorWipeEffectColor = light_ns.struct('AddressableColorWipeEffectColor')
|
||||
AddressableScanEffect = light_ns.class_('AddressableScanEffect', AddressableLightEffect)
|
||||
AddressableTwinkleEffect = light_ns.class_('AddressableTwinkleEffect', AddressableLightEffect)
|
||||
AddressableRandomTwinkleEffect = light_ns.class_('AddressableRandomTwinkleEffect',
|
||||
AddressableLightEffect)
|
||||
AddressableFireworksEffect = light_ns.class_('AddressableFireworksEffect', AddressableLightEffect)
|
||||
AddressableFlickerEffect = light_ns.class_('AddressableFlickerEffect', AddressableLightEffect)
|
||||
|
||||
CONF_STROBE = 'strobe'
|
||||
CONF_FLICKER = 'flicker'
|
||||
CONF_ADDRESSABLE_LAMBDA = 'addressable_lambda'
|
||||
CONF_ADDRESSABLE_RAINBOW = 'addressable_rainbow'
|
||||
CONF_ADDRESSABLE_COLOR_WIPE = 'addressable_color_wipe'
|
||||
CONF_ADDRESSABLE_SCAN = 'addressable_scan'
|
||||
CONF_ADDRESSABLE_TWINKLE = 'addressable_twinkle'
|
||||
CONF_ADDRESSABLE_RANDOM_TWINKLE = 'addressable_random_twinkle'
|
||||
CONF_ADDRESSABLE_FIREWORKS = 'addressable_fireworks'
|
||||
CONF_ADDRESSABLE_FLICKER = 'addressable_flicker'
|
||||
|
||||
CONF_ADD_LED_INTERVAL = 'add_led_interval'
|
||||
CONF_REVERSE = 'reverse'
|
||||
CONF_MOVE_INTERVAL = 'move_interval'
|
||||
CONF_TWINKLE_PROBABILITY = 'twinkle_probability'
|
||||
CONF_PROGRESS_INTERVAL = 'progress_interval'
|
||||
CONF_SPARK_PROBABILITY = 'spark_probability'
|
||||
CONF_USE_RANDOM_COLOR = 'use_random_color'
|
||||
CONF_FADE_OUT_RATE = 'fade_out_rate'
|
||||
CONF_INTENSITY = 'intensity'
|
||||
|
||||
BINARY_EFFECTS = [CONF_LAMBDA, CONF_STROBE]
|
||||
MONOCHROMATIC_EFFECTS = BINARY_EFFECTS + [CONF_FLICKER]
|
||||
RGB_EFFECTS = MONOCHROMATIC_EFFECTS + [CONF_RANDOM]
|
||||
ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_RAINBOW,
|
||||
CONF_ADDRESSABLE_COLOR_WIPE, CONF_ADDRESSABLE_SCAN,
|
||||
CONF_ADDRESSABLE_TWINKLE, CONF_ADDRESSABLE_RANDOM_TWINKLE,
|
||||
CONF_ADDRESSABLE_FIREWORKS, CONF_ADDRESSABLE_FLICKER]
|
||||
|
||||
EFFECTS_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_LAMBDA): vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_RANDOM): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(RandomLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Random"): cv.string,
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_STROBE): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(StrobeLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Strobe"): cv.string,
|
||||
vol.Optional(CONF_COLORS): vol.All(cv.ensure_list(vol.Schema({
|
||||
vol.Optional(CONF_STATE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||
vol.Required(CONF_DURATION): cv.positive_time_period_milliseconds,
|
||||
}), cv.has_at_least_one_key(CONF_STATE, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE,
|
||||
CONF_WHITE)), vol.Length(min=2)),
|
||||
}),
|
||||
vol.Optional(CONF_FLICKER): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(FlickerLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Flicker"): cv.string,
|
||||
vol.Optional(CONF_ALPHA): cv.percentage,
|
||||
vol.Optional(CONF_INTENSITY): cv.percentage,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_LAMBDA): vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_RAINBOW): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRainbowLightEffect),
|
||||
vol.Optional(CONF_NAME, default="Rainbow"): cv.string,
|
||||
vol.Optional(CONF_SPEED): cv.uint32_t,
|
||||
vol.Optional(CONF_WIDTH): cv.uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_COLOR_WIPE): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableColorWipeEffect),
|
||||
vol.Optional(CONF_NAME, default="Color Wipe"): cv.string,
|
||||
vol.Optional(CONF_COLORS): cv.ensure_list({
|
||||
vol.Optional(CONF_RED, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||
vol.Optional(CONF_RANDOM, default=False): cv.boolean,
|
||||
vol.Required(CONF_NUM_LEDS): vol.All(cv.uint32_t, vol.Range(min=1)),
|
||||
}),
|
||||
vol.Optional(CONF_ADD_LED_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_REVERSE): cv.boolean,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_SCAN): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableScanEffect),
|
||||
vol.Optional(CONF_NAME, default="Scan"): cv.string,
|
||||
vol.Optional(CONF_MOVE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_TWINKLE): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableTwinkleEffect),
|
||||
vol.Optional(CONF_NAME, default="Twinkle"): cv.string,
|
||||
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_RANDOM_TWINKLE): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRandomTwinkleEffect),
|
||||
vol.Optional(CONF_NAME, default="Random Twinkle"): cv.string,
|
||||
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_FIREWORKS): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFireworksEffect),
|
||||
vol.Optional(CONF_NAME, default="Fireworks"): cv.string,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_SPARK_PROBABILITY): cv.percentage,
|
||||
vol.Optional(CONF_USE_RANDOM_COLOR): cv.boolean,
|
||||
vol.Optional(CONF_FADE_OUT_RATE): cv.uint8_t,
|
||||
}),
|
||||
vol.Optional(CONF_ADDRESSABLE_FLICKER): vol.Schema({
|
||||
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFlickerEffect),
|
||||
vol.Optional(CONF_NAME, default="Addressable Flicker"): cv.string,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_INTENSITY): cv.percentage,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
def validate_effects(allowed_effects):
|
||||
def validator(value):
|
||||
is_list = isinstance(value, list)
|
||||
if not is_list:
|
||||
value = [value]
|
||||
names = set()
|
||||
ret = []
|
||||
errors = []
|
||||
for i, effect in enumerate(value):
|
||||
path = [i] if is_list else []
|
||||
if not isinstance(effect, dict):
|
||||
errors.append(
|
||||
vol.Invalid("Each effect must be a dictionary, not {}".format(type(value)),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
if len(effect) > 1:
|
||||
errors.append(
|
||||
vol.Invalid("Each entry in the 'effects:' option must be a single effect.",
|
||||
path)
|
||||
)
|
||||
continue
|
||||
if not effect:
|
||||
errors.append(
|
||||
vol.Invalid("Found no effect for the {}th entry in 'effects:'!".format(i),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
key = next(iter(effect.keys()))
|
||||
if key.startswith('fastled'):
|
||||
errors.append(
|
||||
vol.Invalid("FastLED effects have been renamed to addressable effects. "
|
||||
"Please use '{}'".format(key.replace('fastled', 'addressable')),
|
||||
path)
|
||||
)
|
||||
continue
|
||||
if key not in allowed_effects:
|
||||
errors.append(
|
||||
vol.Invalid("The effect '{}' does not exist or is not allowed for this "
|
||||
"light type".format(key), path)
|
||||
)
|
||||
continue
|
||||
effect[key] = effect[key] or {}
|
||||
try:
|
||||
conf = EFFECTS_SCHEMA(effect)
|
||||
except vol.Invalid as err:
|
||||
err.prepend(path)
|
||||
errors.append(err)
|
||||
continue
|
||||
name = conf[key][CONF_NAME]
|
||||
if name in names:
|
||||
errors.append(
|
||||
vol.Invalid(u"Found the effect name '{}' twice. All effects must have "
|
||||
u"unique names".format(name), [i])
|
||||
)
|
||||
continue
|
||||
names.add(name)
|
||||
ret.append(conf)
|
||||
if errors:
|
||||
raise vol.MultipleInvalid(errors)
|
||||
return ret
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(LightState),
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTJSONLightComponent),
|
||||
})
|
||||
|
||||
LIGHT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(LIGHT_SCHEMA.schema)
|
||||
|
||||
|
||||
def build_effect(full_config):
|
||||
key, config = next(iter(full_config.items()))
|
||||
if key == CONF_LAMBDA:
|
||||
lambda_ = None
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [], return_type=void):
|
||||
yield None
|
||||
yield LambdaLightEffect.new(config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL])
|
||||
elif key == CONF_RANDOM:
|
||||
rhs = RandomLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_STROBE:
|
||||
rhs = StrobeLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
colors = []
|
||||
for color in config.get(CONF_COLORS, []):
|
||||
colors.append(StructInitializer(
|
||||
StrobeLightEffectColor,
|
||||
('color', LightColorValues(color[CONF_STATE], color[CONF_BRIGHTNESS],
|
||||
color[CONF_RED], color[CONF_GREEN], color[CONF_BLUE],
|
||||
color[CONF_WHITE])),
|
||||
('duration', color[CONF_DURATION]),
|
||||
))
|
||||
if colors:
|
||||
add(effect.set_colors(colors))
|
||||
yield effect
|
||||
elif key == CONF_FLICKER:
|
||||
rhs = FlickerLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_ALPHA in config:
|
||||
add(effect.set_alpha(config[CONF_ALPHA]))
|
||||
if CONF_INTENSITY in config:
|
||||
add(effect.set_intensity(config[CONF_INTENSITY]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_LAMBDA:
|
||||
args = [(AddressableLightRef, 'it')]
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], args, return_type=void):
|
||||
yield None
|
||||
yield AddressableLambdaLightEffect.new(config[CONF_NAME], lambda_,
|
||||
config[CONF_UPDATE_INTERVAL])
|
||||
elif key == CONF_ADDRESSABLE_RAINBOW:
|
||||
rhs = AddressableRainbowLightEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_SPEED in config:
|
||||
add(effect.set_speed(config[CONF_SPEED]))
|
||||
if CONF_WIDTH in config:
|
||||
add(effect.set_width(config[CONF_WIDTH]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_COLOR_WIPE:
|
||||
rhs = AddressableColorWipeEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_ADD_LED_INTERVAL in config:
|
||||
add(effect.set_add_led_interval(config[CONF_ADD_LED_INTERVAL]))
|
||||
if CONF_REVERSE in config:
|
||||
add(effect.set_reverse(config[CONF_REVERSE]))
|
||||
colors = []
|
||||
for color in config.get(CONF_COLORS, []):
|
||||
colors.append(StructInitializer(
|
||||
AddressableColorWipeEffectColor,
|
||||
('r', int(round(color[CONF_RED] * 255))),
|
||||
('g', int(round(color[CONF_GREEN] * 255))),
|
||||
('b', int(round(color[CONF_BLUE] * 255))),
|
||||
('w', int(round(color[CONF_WHITE] * 255))),
|
||||
('random', color[CONF_RANDOM]),
|
||||
('num_leds', color[CONF_NUM_LEDS]),
|
||||
))
|
||||
if colors:
|
||||
add(effect.set_colors(colors))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_SCAN:
|
||||
rhs = AddressableScanEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_MOVE_INTERVAL in config:
|
||||
add(effect.set_move_interval(config[CONF_MOVE_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_TWINKLE:
|
||||
rhs = AddressableTwinkleEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TWINKLE_PROBABILITY in config:
|
||||
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||
if CONF_PROGRESS_INTERVAL in config:
|
||||
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_RANDOM_TWINKLE:
|
||||
rhs = AddressableRandomTwinkleEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_TWINKLE_PROBABILITY in config:
|
||||
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
|
||||
if CONF_PROGRESS_INTERVAL in config:
|
||||
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_FIREWORKS:
|
||||
rhs = AddressableFireworksEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
if CONF_SPARK_PROBABILITY in config:
|
||||
add(effect.set_spark_probability(config[CONF_SPARK_PROBABILITY]))
|
||||
if CONF_USE_RANDOM_COLOR in config:
|
||||
add(effect.set_spark_probability(config[CONF_USE_RANDOM_COLOR]))
|
||||
if CONF_FADE_OUT_RATE in config:
|
||||
add(effect.set_spark_probability(config[CONF_FADE_OUT_RATE]))
|
||||
yield effect
|
||||
elif key == CONF_ADDRESSABLE_FLICKER:
|
||||
rhs = AddressableFlickerEffect.new(config[CONF_NAME])
|
||||
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
if CONF_INTENSITY in config:
|
||||
add(effect.set_intensity(config[CONF_INTENSITY]))
|
||||
yield effect
|
||||
else:
|
||||
raise NotImplementedError("Effect {} not implemented".format(next(config.keys())))
|
||||
|
||||
|
||||
def setup_light_core_(light_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(light_var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEFAULT_TRANSITION_LENGTH in config:
|
||||
add(light_var.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH]))
|
||||
if CONF_GAMMA_CORRECT in config:
|
||||
add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT]))
|
||||
effects = []
|
||||
for conf in config.get(CONF_EFFECTS, []):
|
||||
for effect in build_effect(conf):
|
||||
yield
|
||||
effects.append(effect)
|
||||
if effects:
|
||||
add(light_var.add_effects(effects))
|
||||
|
||||
setup_mqtt_component(light_var.Pget_mqtt(), config)
|
||||
|
||||
|
||||
def setup_light(light_obj, config):
|
||||
light_var = Pvariable(config[CONF_ID], light_obj, has_side_effects=False)
|
||||
CORE.add_job(setup_light_core_, light_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_LIGHT'
|
||||
|
||||
CONF_LIGHT_TOGGLE = 'light.toggle'
|
||||
LIGHT_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TOGGLE, LIGHT_TOGGLE_ACTION_SCHEMA)
|
||||
def light_toggle_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_toggle_action(template_arg)
|
||||
type = ToggleAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
for template_ in templatable(config[CONF_TRANSITION_LENGTH], arg_type, uint32):
|
||||
yield None
|
||||
add(action.set_transition_length(template_))
|
||||
yield action
|
||||
|
||||
|
||||
CONF_LIGHT_TURN_OFF = 'light.turn_off'
|
||||
LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
|
||||
def light_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_off_action(template_arg)
|
||||
type = TurnOffAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
for template_ in templatable(config[CONF_TRANSITION_LENGTH], arg_type, uint32):
|
||||
yield None
|
||||
add(action.set_transition_length(template_))
|
||||
yield action
|
||||
|
||||
|
||||
CONF_LIGHT_TURN_ON = 'light.turn_on'
|
||||
LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(LightState),
|
||||
vol.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
|
||||
cv.templatable(cv.positive_time_period_milliseconds),
|
||||
vol.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
|
||||
cv.templatable(cv.positive_time_period_milliseconds),
|
||||
vol.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_RED): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_GREEN): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.positive_float),
|
||||
vol.Optional(CONF_EFFECT): cv.templatable(cv.string),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA)
|
||||
def light_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_on_action(template_arg)
|
||||
type = TurnOnAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_TRANSITION_LENGTH in config:
|
||||
for template_ in templatable(config[CONF_TRANSITION_LENGTH], arg_type, uint32):
|
||||
yield None
|
||||
add(action.set_transition_length(template_))
|
||||
if CONF_FLASH_LENGTH in config:
|
||||
for template_ in templatable(config[CONF_FLASH_LENGTH], arg_type, uint32):
|
||||
yield None
|
||||
add(action.set_flash_length(template_))
|
||||
if CONF_BRIGHTNESS in config:
|
||||
for template_ in templatable(config[CONF_BRIGHTNESS], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_brightness(template_))
|
||||
if CONF_RED in config:
|
||||
for template_ in templatable(config[CONF_RED], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_red(template_))
|
||||
if CONF_GREEN in config:
|
||||
for template_ in templatable(config[CONF_GREEN], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_green(template_))
|
||||
if CONF_BLUE in config:
|
||||
for template_ in templatable(config[CONF_BLUE], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_blue(template_))
|
||||
if CONF_WHITE in config:
|
||||
for template_ in templatable(config[CONF_WHITE], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_white(template_))
|
||||
if CONF_COLOR_TEMPERATURE in config:
|
||||
for template_ in templatable(config[CONF_COLOR_TEMPERATURE], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_color_temperature(template_))
|
||||
if CONF_EFFECT in config:
|
||||
for template_ in templatable(config[CONF_EFFECT], arg_type, std_string):
|
||||
yield None
|
||||
add(action.set_effect(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=True,
|
||||
white_value=True):
|
||||
ret = mqtt.build_hass_config(data, 'light', config, include_state=True, include_command=True)
|
||||
if ret is None:
|
||||
return None
|
||||
ret['schema'] = 'json'
|
||||
if brightness:
|
||||
ret['brightness'] = True
|
||||
if rgb:
|
||||
ret['rgb'] = True
|
||||
if color_temp:
|
||||
ret['color_temp'] = True
|
||||
if white_value:
|
||||
ret['white_value'] = True
|
||||
for effect in config.get(CONF_EFFECTS, []):
|
||||
ret["effect"] = True
|
||||
effects = ret.setdefault("effect_list", [])
|
||||
effects.append(next(x for x in effect.values())[CONF_NAME])
|
||||
return ret
|
||||
28
esphome/components/light/binary.py
Normal file
28
esphome/components/light/binary.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_EFFECTS, CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.BINARY_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for output_ in get_variable(config[CONF_OUTPUT]):
|
||||
yield
|
||||
rhs = App.make_binary_light(config[CONF_NAME], output_)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=False, rgb=False, color_temp=False,
|
||||
white_value=False)
|
||||
42
esphome/components/light/cwww.py
Normal file
42
esphome/components/light/cwww.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
from esphome.components.light.rgbww import validate_cold_white_colder, \
|
||||
validate_color_temperature
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \
|
||||
CONF_NAME, CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
|
||||
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
|
||||
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for cold_white in get_variable(config[CONF_COLD_WHITE]):
|
||||
yield
|
||||
for warm_white in get_variable(config[CONF_WARM_WHITE]):
|
||||
yield
|
||||
rhs = App.make_cwww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
|
||||
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
|
||||
cold_white, warm_white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=False, color_temp=True,
|
||||
white_value=False)
|
||||
112
esphome/components/light/fastled_clockless.py
Normal file
112
esphome/components/light/fastled_clockless.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHIPSET, CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, \
|
||||
CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, \
|
||||
CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_RGB_ORDER
|
||||
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
TYPES = [
|
||||
'NEOPIXEL',
|
||||
'TM1829',
|
||||
'TM1809',
|
||||
'TM1804',
|
||||
'TM1803',
|
||||
'UCS1903',
|
||||
'UCS1903B',
|
||||
'UCS1904',
|
||||
'UCS2903',
|
||||
'WS2812',
|
||||
'WS2852',
|
||||
'WS2812B',
|
||||
'SK6812',
|
||||
'SK6822',
|
||||
'APA106',
|
||||
'PL9823',
|
||||
'WS2811',
|
||||
'WS2813',
|
||||
'APA104',
|
||||
'WS2811_400',
|
||||
'GW6205',
|
||||
'GW6205_400',
|
||||
'LPD1886',
|
||||
'LPD1886_8BIT',
|
||||
]
|
||||
|
||||
RGB_ORDERS = [
|
||||
'RGB',
|
||||
'RBG',
|
||||
'GRB',
|
||||
'GBR',
|
||||
'BRG',
|
||||
'BGR',
|
||||
]
|
||||
|
||||
|
||||
def validate(value):
|
||||
if value[CONF_CHIPSET] == 'NEOPIXEL' and CONF_RGB_ORDER in value:
|
||||
raise vol.Invalid("NEOPIXEL doesn't support RGB order")
|
||||
return value
|
||||
|
||||
|
||||
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
|
||||
|
||||
vol.Required(CONF_CHIPSET): cv.one_of(*TYPES, upper=True),
|
||||
vol.Required(CONF_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
|
||||
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)),
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), validate)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_fast_led_light(config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs)
|
||||
fast_led = make.Pfast_led
|
||||
|
||||
rgb_order = None
|
||||
if CONF_RGB_ORDER in config:
|
||||
rgb_order = RawExpression(config[CONF_RGB_ORDER])
|
||||
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
|
||||
config[CONF_PIN], rgb_order)
|
||||
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
add(fast_led.set_power_supply(power_supply))
|
||||
|
||||
if CONF_COLOR_CORRECT in config:
|
||||
r, g, b = config[CONF_COLOR_CORRECT]
|
||||
add(fast_led.set_correction(r, g, b))
|
||||
|
||||
light.setup_light(make.Pstate, config)
|
||||
setup_component(fast_led, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
|
||||
|
||||
LIB_DEPS = 'FastLED@3.2.0'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
|
||||
white_value=False)
|
||||
92
esphome/components/light/fastled_spi.py
Normal file
92
esphome/components/light/fastled_spi.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_COLOR_CORRECT, CONF_DATA_PIN, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \
|
||||
CONF_MAX_REFRESH_RATE, CONF_NAME, CONF_NUM_LEDS, CONF_POWER_SUPPLY, CONF_RGB_ORDER
|
||||
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
CHIPSETS = [
|
||||
'LPD8806',
|
||||
'WS2801',
|
||||
'WS2803',
|
||||
'SM16716',
|
||||
'P9813',
|
||||
'APA102',
|
||||
'SK9822',
|
||||
'DOTSTAR',
|
||||
]
|
||||
|
||||
RGB_ORDERS = [
|
||||
'RGB',
|
||||
'RBG',
|
||||
'GRB',
|
||||
'GBR',
|
||||
'BRG',
|
||||
'BGR',
|
||||
]
|
||||
|
||||
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
|
||||
|
||||
vol.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
vol.Required(CONF_DATA_PIN): pins.output_pin,
|
||||
vol.Required(CONF_CLOCK_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
|
||||
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)),
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_fast_led_light(config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs)
|
||||
fast_led = make.Pfast_led
|
||||
|
||||
rgb_order = None
|
||||
if CONF_RGB_ORDER in config:
|
||||
rgb_order = RawExpression(config[CONF_RGB_ORDER])
|
||||
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
|
||||
config[CONF_DATA_PIN],
|
||||
config[CONF_CLOCK_PIN],
|
||||
rgb_order)
|
||||
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
add(fast_led.set_power_supply(power_supply))
|
||||
|
||||
if CONF_COLOR_CORRECT in config:
|
||||
r, g, b = config[CONF_COLOR_CORRECT]
|
||||
add(fast_led.set_correction(r, g, b))
|
||||
|
||||
light.setup_light(make.Pstate, config)
|
||||
setup_component(fast_led, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
|
||||
|
||||
LIB_DEPS = 'FastLED@3.2.0'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
|
||||
white_value=False)
|
||||
31
esphome/components/light/monochromatic.py
Normal file
31
esphome/components/light/monochromatic.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, \
|
||||
CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for output_ in get_variable(config[CONF_OUTPUT]):
|
||||
yield
|
||||
rhs = App.make_monochromatic_light(config[CONF_NAME], output_)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=False, color_temp=False,
|
||||
white_value=False)
|
||||
194
esphome/components/light/neopixelbus.py
Normal file
194
esphome/components/light/neopixelbus.py
Normal file
@@ -0,0 +1,194 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.components.light import AddressableLight
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CLOCK_PIN, CONF_COLOR_CORRECT, CONF_DATA_PIN, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_METHOD, \
|
||||
CONF_NAME, CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import TemplateArguments, add, get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application, Component, global_ns
|
||||
|
||||
NeoPixelBusLightOutputBase = light.light_ns.class_('NeoPixelBusLightOutputBase', Component,
|
||||
AddressableLight)
|
||||
ESPNeoPixelOrder = light.light_ns.namespace('ESPNeoPixelOrder')
|
||||
|
||||
|
||||
def validate_type(value):
|
||||
value = cv.string(value).upper()
|
||||
if 'R' not in value:
|
||||
raise vol.Invalid("Must have R in type")
|
||||
if 'G' not in value:
|
||||
raise vol.Invalid("Must have G in type")
|
||||
if 'B' not in value:
|
||||
raise vol.Invalid("Must have B in type")
|
||||
rest = set(value) - set('RGBW')
|
||||
if rest:
|
||||
raise vol.Invalid("Type has invalid color: {}".format(', '.join(rest)))
|
||||
if len(set(value)) != len(value):
|
||||
raise vol.Invalid("Type has duplicate color!")
|
||||
return value
|
||||
|
||||
|
||||
def validate_variant(value):
|
||||
value = cv.string(value).upper()
|
||||
if value == 'WS2813':
|
||||
value = 'WS2812X'
|
||||
if value == 'WS2812':
|
||||
value = '800KBPS'
|
||||
if value == 'LC8812':
|
||||
value = 'SK6812'
|
||||
return cv.one_of(*VARIANTS)(value)
|
||||
|
||||
|
||||
def validate_method(value):
|
||||
if value is None:
|
||||
if CORE.is_esp32:
|
||||
return 'ESP32_I2S_1'
|
||||
if CORE.is_esp8266:
|
||||
return 'ESP8266_DMA'
|
||||
raise NotImplementedError
|
||||
|
||||
if CORE.is_esp32:
|
||||
return cv.one_of(*ESP32_METHODS, upper=True, space='_')(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*ESP8266_METHODS, upper=True, space='_')(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_method_pin(value):
|
||||
method = value[CONF_METHOD]
|
||||
method_pins = {
|
||||
'ESP8266_DMA': [3],
|
||||
'ESP8266_UART0': [1],
|
||||
'ESP8266_ASYNC_UART0': [1],
|
||||
'ESP8266_UART1': [2],
|
||||
'ESP8266_ASYNC_UART1': [2],
|
||||
'ESP32_I2S_0': list(range(0, 32)),
|
||||
'ESP32_I2S_1': list(range(0, 32)),
|
||||
}
|
||||
if CORE.is_esp8266:
|
||||
method_pins['BIT_BANG'] = list(range(0, 16))
|
||||
elif CORE.is_esp32:
|
||||
method_pins['BIT_BANG'] = list(range(0, 32))
|
||||
pins_ = method_pins[method]
|
||||
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
|
||||
if opt in value and value[opt] not in pins_:
|
||||
raise vol.Invalid("Method {} only supports pin(s) {}".format(
|
||||
method, ', '.join('GPIO{}'.format(x) for x in pins_)
|
||||
), path=[CONF_METHOD])
|
||||
return value
|
||||
|
||||
|
||||
VARIANTS = {
|
||||
'WS2812X': 'Ws2812x',
|
||||
'SK6812': 'Sk6812',
|
||||
'800KBPS': '800Kbps',
|
||||
'400KBPS': '400Kbps',
|
||||
}
|
||||
|
||||
ESP8266_METHODS = {
|
||||
'ESP8266_DMA': 'NeoEsp8266Dma{}Method',
|
||||
'ESP8266_UART0': 'NeoEsp8266Uart0{}Method',
|
||||
'ESP8266_UART1': 'NeoEsp8266Uart1{}Method',
|
||||
'ESP8266_ASYNC_UART0': 'NeoEsp8266AsyncUart0{}Method',
|
||||
'ESP8266_ASYNC_UART1': 'NeoEsp8266AsyncUart1{}Method',
|
||||
'BIT_BANG': 'NeoEsp8266BitBang{}Method',
|
||||
}
|
||||
ESP32_METHODS = {
|
||||
'ESP32_I2S_0': 'NeoEsp32I2s0{}Method',
|
||||
'ESP32_I2S_1': 'NeoEsp32I2s1{}Method',
|
||||
'BIT_BANG': 'NeoEsp32BitBang{}Method',
|
||||
}
|
||||
|
||||
|
||||
def format_method(config):
|
||||
variant = VARIANTS[config[CONF_VARIANT]]
|
||||
method = config[CONF_METHOD]
|
||||
if CORE.is_esp8266:
|
||||
return ESP8266_METHODS[method].format(variant)
|
||||
if CORE.is_esp32:
|
||||
return ESP32_METHODS[method].format(variant)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_PIN in config:
|
||||
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
|
||||
raise vol.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
|
||||
return config
|
||||
if CONF_CLOCK_PIN in config:
|
||||
if CONF_DATA_PIN not in config:
|
||||
raise vol.Invalid("If you give clock_pin, you must also specify data_pin")
|
||||
return config
|
||||
raise vol.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'")
|
||||
|
||||
|
||||
MakeNeoPixelBusLight = Application.struct('MakeNeoPixelBusLight')
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeNeoPixelBusLight),
|
||||
|
||||
vol.Optional(CONF_TYPE, default='GRB'): validate_type,
|
||||
vol.Optional(CONF_VARIANT, default='800KBPS'): validate_variant,
|
||||
vol.Optional(CONF_METHOD, default=None): validate_method,
|
||||
vol.Optional(CONF_PIN): pins.output_pin,
|
||||
vol.Optional(CONF_CLOCK_PIN): pins.output_pin,
|
||||
vol.Optional(CONF_DATA_PIN): pins.output_pin,
|
||||
|
||||
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=4)),
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), validate, validate_method_pin)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
type_ = config[CONF_TYPE]
|
||||
has_white = 'W' in type_
|
||||
if has_white:
|
||||
func = App.make_neo_pixel_bus_rgbw_light
|
||||
color_feat = global_ns.NeoRgbwFeature
|
||||
else:
|
||||
func = App.make_neo_pixel_bus_rgb_light
|
||||
color_feat = global_ns.NeoRgbFeature
|
||||
|
||||
template = TemplateArguments(getattr(global_ns, format_method(config)), color_feat)
|
||||
rhs = func(template, config[CONF_NAME])
|
||||
make = variable(config[CONF_MAKE_ID], rhs, type=MakeNeoPixelBusLight.template(template))
|
||||
output = make.Poutput
|
||||
|
||||
if CONF_PIN in config:
|
||||
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_PIN]))
|
||||
else:
|
||||
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_CLOCK_PIN], config[CONF_DATA_PIN]))
|
||||
|
||||
add(output.set_pixel_order(getattr(ESPNeoPixelOrder, type_)))
|
||||
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
add(output.set_power_supply(power_supply))
|
||||
|
||||
if CONF_COLOR_CORRECT in config:
|
||||
add(output.set_correction(*config[CONF_COLOR_CORRECT]))
|
||||
|
||||
light.setup_light(make.Pstate, config)
|
||||
setup_component(output, config)
|
||||
|
||||
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_NEO_PIXEL_BUS_LIGHT'
|
||||
|
||||
LIB_DEPS = 'NeoPixelBus@2.4.1'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
|
||||
white_value='W' in config[CONF_TYPE])
|
||||
37
esphome/components/light/rgb.py
Normal file
37
esphome/components/light/rgb.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \
|
||||
CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for red in get_variable(config[CONF_RED]):
|
||||
yield
|
||||
for green in get_variable(config[CONF_GREEN]):
|
||||
yield
|
||||
for blue in get_variable(config[CONF_BLUE]):
|
||||
yield
|
||||
rhs = App.make_rgb_light(config[CONF_NAME], red, green, blue)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
|
||||
white_value=False)
|
||||
40
esphome/components/light/rgbw.py
Normal file
40
esphome/components/light/rgbw.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \
|
||||
CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED, CONF_WHITE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for red in get_variable(config[CONF_RED]):
|
||||
yield
|
||||
for green in get_variable(config[CONF_GREEN]):
|
||||
yield
|
||||
for blue in get_variable(config[CONF_BLUE]):
|
||||
yield
|
||||
for white in get_variable(config[CONF_WHITE]):
|
||||
yield
|
||||
rhs = App.make_rgbw_light(config[CONF_NAME], red, green, blue, white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
|
||||
white_value=True)
|
||||
70
esphome/components/light/rgbww.py
Normal file
70
esphome/components/light/rgbww.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import light, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BLUE, CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, \
|
||||
CONF_NAME, CONF_RED, CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE
|
||||
from esphome.cpp_generator import get_variable, variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
|
||||
def validate_color_temperature(value):
|
||||
try:
|
||||
val = cv.float_with_unit('Color Temperature', 'mireds')(value)
|
||||
except vol.Invalid:
|
||||
val = 1000000.0 / cv.float_with_unit('Color Temperature', 'K')(value)
|
||||
if val < 0:
|
||||
raise vol.Invalid("Color temperature cannot be negative")
|
||||
return val
|
||||
|
||||
|
||||
def validate_cold_white_colder(value):
|
||||
cw = value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
|
||||
ww = value[CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
||||
if cw > ww:
|
||||
raise vol.Invalid("Cold white color temperature cannot be higher than warm white")
|
||||
if cw == ww:
|
||||
raise vol.Invalid("Cold white color temperature cannot be the same as warm white")
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
|
||||
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
|
||||
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
|
||||
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
|
||||
|
||||
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
|
||||
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for red in get_variable(config[CONF_RED]):
|
||||
yield
|
||||
for green in get_variable(config[CONF_GREEN]):
|
||||
yield
|
||||
for blue in get_variable(config[CONF_BLUE]):
|
||||
yield
|
||||
for cold_white in get_variable(config[CONF_COLD_WHITE]):
|
||||
yield
|
||||
for warm_white in get_variable(config[CONF_WARM_WHITE]):
|
||||
yield
|
||||
rhs = App.make_rgbww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
|
||||
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
|
||||
red, green, blue, cold_white, warm_white)
|
||||
light_struct = variable(config[CONF_MAKE_ID], rhs)
|
||||
light.setup_light(light_struct.Pstate, config)
|
||||
setup_component(light_struct.Pstate, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=True,
|
||||
white_value=True)
|
||||
180
esphome/components/logger.py
Normal file
180
esphome/components/logger.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY, LambdaAction
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \
|
||||
CONF_LEVEL, CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE
|
||||
from esphome.core import CORE, EsphomeError, Lambda
|
||||
from esphome.cpp_generator import Pvariable, RawExpression, add, process_lambda, statement
|
||||
from esphome.cpp_types import App, Component, esphome_ns, global_ns, void
|
||||
from esphome.py_compat import text_type
|
||||
|
||||
LOG_LEVELS = {
|
||||
'NONE': global_ns.ESPHOME_LOG_LEVEL_NONE,
|
||||
'ERROR': global_ns.ESPHOME_LOG_LEVEL_ERROR,
|
||||
'WARN': global_ns.ESPHOME_LOG_LEVEL_WARN,
|
||||
'INFO': global_ns.ESPHOME_LOG_LEVEL_INFO,
|
||||
'DEBUG': global_ns.ESPHOME_LOG_LEVEL_DEBUG,
|
||||
'VERBOSE': global_ns.ESPHOME_LOG_LEVEL_VERBOSE,
|
||||
'VERY_VERBOSE': global_ns.ESPHOME_LOG_LEVEL_VERY_VERBOSE,
|
||||
}
|
||||
|
||||
LOG_LEVEL_TO_ESP_LOG = {
|
||||
'ERROR': global_ns.ESP_LOGE,
|
||||
'WARN': global_ns.ESP_LOGW,
|
||||
'INFO': global_ns.ESP_LOGI,
|
||||
'DEBUG': global_ns.ESP_LOGD,
|
||||
'VERBOSE': global_ns.ESP_LOGV,
|
||||
'VERY_VERBOSE': global_ns.ESP_LOGVV,
|
||||
}
|
||||
|
||||
LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE']
|
||||
|
||||
UART_SELECTION_ESP32 = ['UART0', 'UART1', 'UART2']
|
||||
|
||||
UART_SELECTION_ESP8266 = ['UART0', 'UART0_SWAP', 'UART1']
|
||||
|
||||
HARDWARE_UART_TO_UART_SELECTION = {
|
||||
'UART0': global_ns.UART_SELECTION_UART0,
|
||||
'UART0_SWAP': global_ns.UART_SELECTION_UART0_SWAP,
|
||||
'UART1': global_ns.UART_SELECTION_UART1,
|
||||
'UART2': global_ns.UART_SELECTION_UART2,
|
||||
}
|
||||
|
||||
HARDWARE_UART_TO_SERIAL = {
|
||||
'UART0': 'Serial',
|
||||
'UART0_SWAP': 'Serial',
|
||||
'UART1': 'Serial1',
|
||||
'UART2': 'Serial2',
|
||||
}
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
|
||||
|
||||
def uart_selection(value):
|
||||
if CORE.is_esp32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_local_no_higher_than_global(value):
|
||||
global_level = value.get(CONF_LEVEL, 'DEBUG')
|
||||
for tag, level in value.get(CONF_LOGS, {}).items():
|
||||
if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level):
|
||||
raise EsphomeError(u"The local log level {} for {} must be less severe than the "
|
||||
u"global log level {}.".format(level, tag, global_level))
|
||||
return value
|
||||
|
||||
|
||||
LogComponent = esphome_ns.class_('LogComponent', Component)
|
||||
|
||||
CONFIG_SCHEMA = vol.All(vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(LogComponent),
|
||||
vol.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
|
||||
vol.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
|
||||
vol.Optional(CONF_HARDWARE_UART, default='UART0'): uart_selection,
|
||||
vol.Optional(CONF_LEVEL): is_log_level,
|
||||
vol.Optional(CONF_LOGS): vol.Schema({
|
||||
cv.string: is_log_level,
|
||||
})
|
||||
}), validate_local_no_higher_than_global)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.init_log(config.get(CONF_BAUD_RATE),
|
||||
config.get(CONF_TX_BUFFER_SIZE),
|
||||
HARDWARE_UART_TO_UART_SELECTION[config.get(CONF_HARDWARE_UART)])
|
||||
log = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_LEVEL in config:
|
||||
add(log.set_global_log_level(LOG_LEVELS[config[CONF_LEVEL]]))
|
||||
for tag, level in config.get(CONF_LOGS, {}).items():
|
||||
add(log.set_log_level(tag, LOG_LEVELS[level]))
|
||||
|
||||
|
||||
def required_build_flags(config):
|
||||
flags = []
|
||||
if CONF_LEVEL in config:
|
||||
flags.append(u'-DESPHOME_LOG_LEVEL={}'.format(str(LOG_LEVELS[config[CONF_LEVEL]])))
|
||||
this_severity = LOG_LEVEL_SEVERITY.index(config[CONF_LEVEL])
|
||||
verbose_severity = LOG_LEVEL_SEVERITY.index('VERBOSE')
|
||||
is_at_least_verbose = this_severity >= verbose_severity
|
||||
has_serial_logging = config.get(CONF_BAUD_RATE) != 0
|
||||
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
|
||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
|
||||
flags.append(u"-DDEBUG_ESP_PORT={}".format(debug_serial_port))
|
||||
flags.append(u"-DLWIP_DEBUG")
|
||||
DEBUG_COMPONENTS = {
|
||||
'HTTP_CLIENT',
|
||||
'HTTP_SERVER',
|
||||
'HTTP_UPDATE',
|
||||
'OTA',
|
||||
'SSL',
|
||||
'TLS_MEM',
|
||||
'UPDATER',
|
||||
'WIFI',
|
||||
}
|
||||
for comp in DEBUG_COMPONENTS:
|
||||
flags.append(u"-DDEBUG_ESP_{}".format(comp))
|
||||
if CORE.is_esp32 and is_at_least_verbose:
|
||||
flags.append('-DCORE_DEBUG_LEVEL=5')
|
||||
|
||||
return flags
|
||||
|
||||
|
||||
def maybe_simple_message(schema):
|
||||
def validator(value):
|
||||
if isinstance(value, dict):
|
||||
return vol.Schema(schema)(value)
|
||||
return vol.Schema(schema)({CONF_FORMAT: value})
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def validate_printf(value):
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
# pylint: disable=anomalous-backslash-in-string
|
||||
cfmt = u"""\
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
(?: # first option
|
||||
(?:[-+0 #]{0,5}) # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
) | # OR
|
||||
%%) # literal "%%"
|
||||
""" # noqa
|
||||
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
|
||||
if len(matches) != len(value[CONF_ARGS]):
|
||||
raise vol.Invalid(u"Found {} printf-patterns ({}), but {} args were given!"
|
||||
u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS])))
|
||||
return value
|
||||
|
||||
|
||||
CONF_LOGGER_LOG = 'logger.log'
|
||||
LOGGER_LOG_ACTION_SCHEMA = vol.All(maybe_simple_message({
|
||||
vol.Required(CONF_FORMAT): cv.string,
|
||||
vol.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
vol.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(*LOG_LEVEL_TO_ESP_LOG, upper=True),
|
||||
vol.Optional(CONF_TAG, default="main"): cv.string,
|
||||
}), validate_printf)
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA)
|
||||
def logger_log_action_to_code(config, action_id, arg_type, template_arg):
|
||||
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
|
||||
args = [RawExpression(text_type(x)) for x in config[CONF_ARGS]]
|
||||
|
||||
text = text_type(statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args)))
|
||||
|
||||
for lambda_ in process_lambda(Lambda(text), [(arg_type, 'x')], return_type=void):
|
||||
yield None
|
||||
rhs = LambdaAction.new(template_arg, lambda_)
|
||||
type = LambdaAction.template(template_arg)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
349
esphome/components/mqtt.py
Normal file
349
esphome/components/mqtt.py
Normal file
@@ -0,0 +1,349 @@
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
from esphome.components import logger
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AVAILABILITY, CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CLIENT_ID, \
|
||||
CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, \
|
||||
CONF_ESPHOME, CONF_ID, CONF_INTERNAL, CONF_KEEPALIVE, CONF_LEVEL, CONF_LOG_TOPIC, \
|
||||
CONF_MQTT, CONF_NAME, CONF_ON_JSON_MESSAGE, CONF_ON_MESSAGE, CONF_PASSWORD, CONF_PAYLOAD, \
|
||||
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_PORT, CONF_QOS, CONF_REBOOT_TIMEOUT, \
|
||||
CONF_RETAIN, CONF_SHUTDOWN_MESSAGE, CONF_SSL_FINGERPRINTS, CONF_STATE_TOPIC, CONF_TOPIC, \
|
||||
CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, CONF_USERNAME, CONF_WILL_MESSAGE
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.cpp_generator import Pvariable, RawExpression, StructInitializer, TemplateArguments, \
|
||||
add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_types import Action, App, Component, JsonObjectConstRef, JsonObjectRef, \
|
||||
Trigger, bool_, esphome_ns, optional, std_string, uint8, void
|
||||
|
||||
|
||||
def validate_message_just_topic(value):
|
||||
value = cv.publish_topic(value)
|
||||
return MQTT_MESSAGE_BASE({CONF_TOPIC: value})
|
||||
|
||||
|
||||
MQTT_MESSAGE_BASE = vol.Schema({
|
||||
vol.Required(CONF_TOPIC): cv.publish_topic,
|
||||
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos,
|
||||
vol.Optional(CONF_RETAIN, default=True): cv.boolean,
|
||||
})
|
||||
|
||||
MQTT_MESSAGE_TEMPLATE_SCHEMA = vol.Any(None, MQTT_MESSAGE_BASE, validate_message_just_topic)
|
||||
|
||||
MQTT_MESSAGE_SCHEMA = vol.Any(None, MQTT_MESSAGE_BASE.extend({
|
||||
vol.Required(CONF_PAYLOAD): cv.mqtt_payload,
|
||||
}))
|
||||
|
||||
mqtt_ns = esphome_ns.namespace('mqtt')
|
||||
MQTTMessage = mqtt_ns.struct('MQTTMessage')
|
||||
MQTTClientComponent = mqtt_ns.class_('MQTTClientComponent', Component)
|
||||
MQTTPublishAction = mqtt_ns.class_('MQTTPublishAction', Action)
|
||||
MQTTPublishJsonAction = mqtt_ns.class_('MQTTPublishJsonAction', Action)
|
||||
MQTTMessageTrigger = mqtt_ns.class_('MQTTMessageTrigger', Trigger.template(std_string))
|
||||
MQTTJsonMessageTrigger = mqtt_ns.class_('MQTTJsonMessageTrigger',
|
||||
Trigger.template(JsonObjectConstRef))
|
||||
MQTTComponent = mqtt_ns.class_('MQTTComponent', Component)
|
||||
|
||||
|
||||
def validate_config(value):
|
||||
if CONF_PORT not in value:
|
||||
parts = value[CONF_BROKER].split(u':')
|
||||
if len(parts) == 2:
|
||||
value[CONF_BROKER] = parts[0]
|
||||
value[CONF_PORT] = cv.port(parts[1])
|
||||
else:
|
||||
value[CONF_PORT] = 1883
|
||||
return value
|
||||
|
||||
|
||||
def validate_fingerprint(value):
|
||||
value = cv.string(value)
|
||||
if re.match(r'^[0-9a-f]{40}$', value) is None:
|
||||
raise vol.Invalid(u"fingerprint must be valid SHA1 hash")
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.All(vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(MQTTClientComponent),
|
||||
vol.Required(CONF_BROKER): cv.string_strict,
|
||||
vol.Optional(CONF_PORT): 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): vol.Any(cv.boolean, cv.one_of("CLEAN", upper=True)),
|
||||
vol.Optional(CONF_DISCOVERY_RETAIN): cv.boolean,
|
||||
vol.Optional(CONF_DISCOVERY_PREFIX): cv.publish_topic,
|
||||
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
vol.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
vol.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
vol.Optional(CONF_TOPIC_PREFIX): cv.publish_topic,
|
||||
vol.Optional(CONF_LOG_TOPIC): vol.Any(None, MQTT_MESSAGE_BASE.extend({
|
||||
vol.Optional(CONF_LEVEL): logger.is_log_level,
|
||||
}), validate_message_just_topic),
|
||||
vol.Optional(CONF_SSL_FINGERPRINTS): vol.All(cv.only_on_esp8266,
|
||||
cv.ensure_list(validate_fingerprint)),
|
||||
vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds,
|
||||
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_ON_MESSAGE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTMessageTrigger),
|
||||
vol.Required(CONF_TOPIC): cv.subscribe_topic,
|
||||
vol.Optional(CONF_QOS): cv.mqtt_qos,
|
||||
vol.Optional(CONF_PAYLOAD): cv.string_strict,
|
||||
}),
|
||||
vol.Optional(CONF_ON_JSON_MESSAGE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTJsonMessageTrigger),
|
||||
vol.Required(CONF_TOPIC): cv.subscribe_topic,
|
||||
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos,
|
||||
}),
|
||||
}), validate_config)
|
||||
|
||||
|
||||
def exp_mqtt_message(config):
|
||||
if config is None:
|
||||
return optional(TemplateArguments(MQTTMessage))
|
||||
exp = StructInitializer(
|
||||
MQTTMessage,
|
||||
('topic', config[CONF_TOPIC]),
|
||||
('payload', config.get(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(config[CONF_ID], rhs)
|
||||
|
||||
discovery = config.get(CONF_DISCOVERY, True)
|
||||
discovery_retain = config.get(CONF_DISCOVERY_RETAIN, True)
|
||||
discovery_prefix = config.get(CONF_DISCOVERY_PREFIX, 'homeassistant')
|
||||
|
||||
if not discovery:
|
||||
add(mqtt.disable_discovery())
|
||||
elif discovery == "CLEAN":
|
||||
add(mqtt.set_discovery_info(discovery_prefix, discovery_retain, True))
|
||||
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
|
||||
add(mqtt.set_discovery_info(discovery_prefix, discovery_retain))
|
||||
|
||||
if CONF_TOPIC_PREFIX in config:
|
||||
add(mqtt.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
|
||||
|
||||
if CONF_BIRTH_MESSAGE in config:
|
||||
birth_message = config[CONF_BIRTH_MESSAGE]
|
||||
if not birth_message:
|
||||
add(mqtt.disable_birth_message())
|
||||
else:
|
||||
add(mqtt.set_birth_message(exp_mqtt_message(birth_message)))
|
||||
if CONF_WILL_MESSAGE in config:
|
||||
will_message = config[CONF_WILL_MESSAGE]
|
||||
if not will_message:
|
||||
add(mqtt.disable_last_will())
|
||||
else:
|
||||
add(mqtt.set_last_will(exp_mqtt_message(will_message)))
|
||||
if CONF_SHUTDOWN_MESSAGE in config:
|
||||
shutdown_message = config[CONF_SHUTDOWN_MESSAGE]
|
||||
if not shutdown_message:
|
||||
add(mqtt.disable_shutdown_message())
|
||||
else:
|
||||
add(mqtt.set_shutdown_message(exp_mqtt_message(shutdown_message)))
|
||||
|
||||
if CONF_CLIENT_ID in config:
|
||||
add(mqtt.set_client_id(config[CONF_CLIENT_ID]))
|
||||
|
||||
if CONF_LOG_TOPIC in config:
|
||||
log_topic = config[CONF_LOG_TOPIC]
|
||||
if not log_topic:
|
||||
add(mqtt.disable_log_message())
|
||||
else:
|
||||
add(mqtt.set_log_message_template(exp_mqtt_message(log_topic)))
|
||||
|
||||
if CONF_LEVEL in config:
|
||||
add(mqtt.set_log_level(logger.LOG_LEVELS[config[CONF_LEVEL]]))
|
||||
|
||||
if CONF_SSL_FINGERPRINTS in config:
|
||||
for fingerprint in config[CONF_SSL_FINGERPRINTS]:
|
||||
arr = [RawExpression("0x{}".format(fingerprint[i:i + 2])) for i in range(0, 40, 2)]
|
||||
add(mqtt.add_ssl_fingerprint(arr))
|
||||
|
||||
if CONF_KEEPALIVE in config:
|
||||
add(mqtt.set_keep_alive(config[CONF_KEEPALIVE]))
|
||||
|
||||
if CONF_REBOOT_TIMEOUT in config:
|
||||
add(mqtt.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
for conf in config.get(CONF_ON_MESSAGE, []):
|
||||
rhs = App.register_component(mqtt.make_message_trigger(conf[CONF_TOPIC]))
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
if CONF_QOS in conf:
|
||||
add(trigger.set_qos(conf[CONF_QOS]))
|
||||
if CONF_PAYLOAD in conf:
|
||||
add(trigger.set_payload(conf[CONF_PAYLOAD]))
|
||||
automation.build_automation(trigger, std_string, conf)
|
||||
|
||||
for conf in config.get(CONF_ON_JSON_MESSAGE, []):
|
||||
rhs = mqtt.make_json_message_trigger(conf[CONF_TOPIC], conf[CONF_QOS])
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, JsonObjectConstRef, conf)
|
||||
|
||||
|
||||
CONF_MQTT_PUBLISH = 'mqtt.publish'
|
||||
MQTT_PUBLISH_ACTION_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.use_variable_id(MQTTClientComponent),
|
||||
vol.Required(CONF_TOPIC): cv.templatable(cv.publish_topic),
|
||||
vol.Required(CONF_PAYLOAD): cv.templatable(cv.mqtt_payload),
|
||||
vol.Optional(CONF_QOS): cv.templatable(cv.mqtt_qos),
|
||||
vol.Optional(CONF_RETAIN): cv.templatable(cv.boolean),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH, MQTT_PUBLISH_ACTION_SCHEMA)
|
||||
def mqtt_publish_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_publish_action(template_arg)
|
||||
type = MQTTPublishAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for template_ in templatable(config[CONF_TOPIC], arg_type, std_string):
|
||||
yield None
|
||||
add(action.set_topic(template_))
|
||||
|
||||
for template_ in templatable(config[CONF_PAYLOAD], arg_type, std_string):
|
||||
yield None
|
||||
add(action.set_payload(template_))
|
||||
if CONF_QOS in config:
|
||||
for template_ in templatable(config[CONF_QOS], arg_type, uint8):
|
||||
yield
|
||||
add(action.set_qos(template_))
|
||||
if CONF_RETAIN in config:
|
||||
for template_ in templatable(config[CONF_RETAIN], arg_type, bool_):
|
||||
yield None
|
||||
add(action.set_retain(template_))
|
||||
yield action
|
||||
|
||||
|
||||
CONF_MQTT_PUBLISH_JSON = 'mqtt.publish_json'
|
||||
MQTT_PUBLISH_JSON_ACTION_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.use_variable_id(MQTTClientComponent),
|
||||
vol.Required(CONF_TOPIC): cv.templatable(cv.publish_topic),
|
||||
vol.Required(CONF_PAYLOAD): cv.lambda_,
|
||||
vol.Optional(CONF_QOS): cv.mqtt_qos,
|
||||
vol.Optional(CONF_RETAIN): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH_JSON, MQTT_PUBLISH_JSON_ACTION_SCHEMA)
|
||||
def mqtt_publish_json_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_publish_json_action(template_arg)
|
||||
type = MQTTPublishJsonAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for template_ in templatable(config[CONF_TOPIC], arg_type, std_string):
|
||||
yield None
|
||||
add(action.set_topic(template_))
|
||||
|
||||
for lambda_ in process_lambda(config[CONF_PAYLOAD], [(arg_type, 'x'), (JsonObjectRef, 'root')],
|
||||
return_type=void):
|
||||
yield None
|
||||
add(action.set_payload(lambda_))
|
||||
if CONF_QOS in config:
|
||||
add(action.set_qos(config[CONF_QOS]))
|
||||
if CONF_RETAIN in config:
|
||||
add(action.set_retain(config[CONF_RETAIN]))
|
||||
yield action
|
||||
|
||||
|
||||
def required_build_flags(config):
|
||||
if CONF_SSL_FINGERPRINTS in config:
|
||||
return '-DASYNC_TCP_SSL_ENABLED=1'
|
||||
return None
|
||||
|
||||
|
||||
def get_default_topic_for(data, component_type, name, suffix):
|
||||
whitelist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'
|
||||
sanitized_name = ''.join(x for x in name.lower().replace(' ', '_') if x in whitelist)
|
||||
return '{}/{}/{}/{}'.format(data.topic_prefix, component_type,
|
||||
sanitized_name, suffix)
|
||||
|
||||
|
||||
def build_hass_config(data, component_type, config, include_state=True, include_command=True):
|
||||
if config.get(CONF_INTERNAL, False):
|
||||
return None
|
||||
ret = OrderedDict()
|
||||
ret['platform'] = 'mqtt'
|
||||
ret['name'] = config[CONF_NAME]
|
||||
if include_state:
|
||||
default = get_default_topic_for(data, component_type, config[CONF_NAME], 'state')
|
||||
ret['state_topic'] = config.get(CONF_STATE_TOPIC, default)
|
||||
if include_command:
|
||||
default = get_default_topic_for(data, component_type, config[CONF_NAME], 'command')
|
||||
ret['command_topic'] = config.get(CONF_STATE_TOPIC, default)
|
||||
avail = config.get(CONF_AVAILABILITY, data.availability)
|
||||
if avail:
|
||||
ret['availability_topic'] = avail[CONF_TOPIC]
|
||||
payload_available = avail[CONF_PAYLOAD_AVAILABLE]
|
||||
if payload_available != 'online':
|
||||
ret['payload_available'] = payload_available
|
||||
payload_not_available = avail[CONF_PAYLOAD_NOT_AVAILABLE]
|
||||
if payload_not_available != 'offline':
|
||||
ret['payload_not_available'] = payload_not_available
|
||||
return ret
|
||||
|
||||
|
||||
class GenerateHassConfigData(object):
|
||||
def __init__(self, config):
|
||||
if 'mqtt' not in config:
|
||||
raise EsphomeError("Cannot generate Home Assistant MQTT config if MQTT is not "
|
||||
"used!")
|
||||
mqtt = config[CONF_MQTT]
|
||||
self.topic_prefix = mqtt.get(CONF_TOPIC_PREFIX, config[CONF_ESPHOME][CONF_NAME])
|
||||
birth_message = mqtt.get(CONF_BIRTH_MESSAGE)
|
||||
if CONF_BIRTH_MESSAGE not in mqtt:
|
||||
birth_message = {
|
||||
CONF_TOPIC: self.topic_prefix + '/status',
|
||||
CONF_PAYLOAD: 'online',
|
||||
}
|
||||
will_message = mqtt.get(CONF_WILL_MESSAGE)
|
||||
if CONF_WILL_MESSAGE not in mqtt:
|
||||
will_message = {
|
||||
CONF_TOPIC: self.topic_prefix + '/status',
|
||||
CONF_PAYLOAD: 'offline'
|
||||
}
|
||||
if not birth_message or not will_message:
|
||||
self.availability = None
|
||||
elif birth_message[CONF_TOPIC] != will_message[CONF_TOPIC]:
|
||||
self.availability = None
|
||||
else:
|
||||
self.availability = {
|
||||
CONF_TOPIC: birth_message[CONF_TOPIC],
|
||||
CONF_PAYLOAD_AVAILABLE: birth_message[CONF_PAYLOAD],
|
||||
CONF_PAYLOAD_NOT_AVAILABLE: will_message[CONF_PAYLOAD],
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
if not availability:
|
||||
add(obj.disable_availability())
|
||||
else:
|
||||
add(obj.set_availability(availability[CONF_TOPIC], availability[CONF_PAYLOAD_AVAILABLE],
|
||||
availability[CONF_PAYLOAD_NOT_AVAILABLE]))
|
||||
|
||||
|
||||
LIB_DEPS = 'AsyncMqttClient@0.8.2'
|
||||
BUILD_FLAGS = '-DUSE_MQTT'
|
||||
46
esphome/components/my9231.py
Normal file
46
esphome/components/my9231.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (CONF_BIT_DEPTH, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID,
|
||||
CONF_NUM_CHANNELS, CONF_NUM_CHIPS, CONF_UPDATE_ON_BOOT)
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
MY9231OutputComponent = output.output_ns.class_('MY9231OutputComponent', Component)
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(MY9231OutputComponent),
|
||||
vol.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_NUM_CHANNELS): vol.All(vol.Coerce(int),
|
||||
vol.Range(3, 1020)),
|
||||
vol.Optional(CONF_NUM_CHIPS): vol.All(vol.Coerce(int),
|
||||
vol.Range(1, 255)),
|
||||
vol.Optional(CONF_BIT_DEPTH): cv.one_of(8, 12, 14, 16, int=True),
|
||||
vol.Optional(CONF_UPDATE_ON_BOOT): vol.Coerce(bool),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for di in gpio_output_pin_expression(config[CONF_DATA_PIN]):
|
||||
yield
|
||||
for dcki in gpio_output_pin_expression(config[CONF_CLOCK_PIN]):
|
||||
yield
|
||||
rhs = App.make_my9231_component(di, dcki)
|
||||
my9231 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_NUM_CHANNELS in config:
|
||||
add(my9231.set_num_channels(config[CONF_NUM_CHANNELS]))
|
||||
if CONF_NUM_CHIPS in config:
|
||||
add(my9231.set_num_chips(config[CONF_NUM_CHIPS]))
|
||||
if CONF_BIT_DEPTH in config:
|
||||
add(my9231.set_bit_depth(config[CONF_BIT_DEPTH]))
|
||||
if CONF_UPDATE_ON_BOOT in config:
|
||||
add(my9231.set_update(config[CONF_UPDATE_ON_BOOT]))
|
||||
setup_component(my9231, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_MY9231_OUTPUT'
|
||||
57
esphome/components/ota.py
Normal file
57
esphome/components/ota.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_OTA, CONF_PASSWORD, CONF_PORT, CONF_SAFE_MODE
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
OTAComponent = esphome_ns.class_('OTAComponent', Component)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(OTAComponent),
|
||||
vol.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_PORT): cv.port,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.init_ota()
|
||||
ota = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_PASSWORD in config:
|
||||
add(ota.set_auth_password(config[CONF_PASSWORD]))
|
||||
if CONF_PORT in config:
|
||||
add(ota.set_port(config[CONF_PORT]))
|
||||
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 CORE.is_esp32:
|
||||
return 3232
|
||||
if CORE.is_esp8266:
|
||||
return 8266
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_auth(config):
|
||||
return config[CONF_OTA].get(CONF_PASSWORD, '')
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_OTA'
|
||||
REQUIRED_BUILD_FLAGS = '-DUSE_NEW_OTA'
|
||||
|
||||
|
||||
def lib_deps(config):
|
||||
if CORE.is_esp32:
|
||||
return ['Update', 'ESPmDNS']
|
||||
if CORE.is_esp8266:
|
||||
return ['Hash', 'ESP8266mDNS']
|
||||
raise NotImplementedError
|
||||
110
esphome/components/output/__init__.py
Normal file
110
esphome/components/output/__init__.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
from esphome.components.power_supply import PowerSupplyComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INVERTED, CONF_LEVEL, CONF_MAX_POWER, CONF_POWER_SUPPLY
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
|
||||
from esphome.cpp_types import Action, esphome_ns, float_
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
BINARY_OUTPUT_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
|
||||
vol.Optional(CONF_INVERTED): cv.boolean,
|
||||
})
|
||||
|
||||
BINARY_OUTPUT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BINARY_OUTPUT_SCHEMA.schema)
|
||||
|
||||
FLOAT_OUTPUT_SCHEMA = BINARY_OUTPUT_SCHEMA.extend({
|
||||
vol.Optional(CONF_MAX_POWER): cv.percentage,
|
||||
})
|
||||
|
||||
FLOAT_OUTPUT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(FLOAT_OUTPUT_SCHEMA.schema)
|
||||
|
||||
output_ns = esphome_ns.namespace('output')
|
||||
BinaryOutput = output_ns.class_('BinaryOutput')
|
||||
BinaryOutputPtr = BinaryOutput.operator('ptr')
|
||||
FloatOutput = output_ns.class_('FloatOutput', BinaryOutput)
|
||||
FloatOutputPtr = FloatOutput.operator('ptr')
|
||||
|
||||
# Actions
|
||||
TurnOffAction = output_ns.class_('TurnOffAction', Action)
|
||||
TurnOnAction = output_ns.class_('TurnOnAction', Action)
|
||||
SetLevelAction = output_ns.class_('SetLevelAction', Action)
|
||||
|
||||
|
||||
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 = None
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
add(obj.set_power_supply(power_supply))
|
||||
if CONF_MAX_POWER in config:
|
||||
add(obj.set_max_power(config[CONF_MAX_POWER]))
|
||||
|
||||
|
||||
def setup_output_platform(obj, config, skip_power_supply=False):
|
||||
CORE.add_job(setup_output_platform_, obj, config, skip_power_supply)
|
||||
|
||||
|
||||
def register_output(var, config):
|
||||
output_var = Pvariable(config[CONF_ID], var, has_side_effects=True)
|
||||
CORE.add_job(setup_output_platform_, output_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_OUTPUT'
|
||||
|
||||
CONF_OUTPUT_TURN_ON = 'output.turn_on'
|
||||
OUTPUT_TURN_ON_ACTION = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(BinaryOutput),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_ON, OUTPUT_TURN_ON_ACTION)
|
||||
def output_turn_on_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_on_action(template_arg)
|
||||
type = TurnOnAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_OUTPUT_TURN_OFF = 'output.turn_off'
|
||||
OUTPUT_TURN_OFF_ACTION = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(BinaryOutput)
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_OFF, OUTPUT_TURN_OFF_ACTION)
|
||||
def output_turn_off_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_turn_off_action(template_arg)
|
||||
type = TurnOffAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_OUTPUT_SET_LEVEL = 'output.set_level'
|
||||
OUTPUT_SET_LEVEL_ACTION = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(FloatOutput),
|
||||
vol.Required(CONF_LEVEL): cv.templatable(cv.percentage),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_OUTPUT_SET_LEVEL, OUTPUT_SET_LEVEL_ACTION)
|
||||
def output_set_level_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_set_level_action(template_arg)
|
||||
type = SetLevelAction.template(arg_type)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
for template_ in templatable(config[CONF_LEVEL], arg_type, float_):
|
||||
yield None
|
||||
add(action.set_level(template_))
|
||||
yield action
|
||||
68
esphome/components/output/custom.py
Normal file
68
esphome/components/output/custom.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE
|
||||
from esphome.cpp_generator import process_lambda, variable
|
||||
from esphome.cpp_types import std_vector
|
||||
|
||||
CustomBinaryOutputConstructor = output.output_ns.class_('CustomBinaryOutputConstructor')
|
||||
CustomFloatOutputConstructor = output.output_ns.class_('CustomFloatOutputConstructor')
|
||||
|
||||
BINARY_SCHEMA = output.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CustomBinaryOutputConstructor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Required(CONF_TYPE): 'binary',
|
||||
vol.Required(CONF_OUTPUTS):
|
||||
cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(output.BinaryOutput),
|
||||
})),
|
||||
})
|
||||
|
||||
FLOAT_SCHEMA = output.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CustomFloatOutputConstructor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Required(CONF_TYPE): 'float',
|
||||
vol.Required(CONF_OUTPUTS):
|
||||
cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(output.FloatOutput),
|
||||
})),
|
||||
})
|
||||
|
||||
|
||||
def validate_custom_output(value):
|
||||
if not isinstance(value, dict):
|
||||
raise vol.Invalid("Value must be dict")
|
||||
if CONF_TYPE not in value:
|
||||
raise vol.Invalid("type not specified!")
|
||||
type = cv.string_strict(value[CONF_TYPE]).lower()
|
||||
value[CONF_TYPE] = type
|
||||
if type == 'binary':
|
||||
return BINARY_SCHEMA(value)
|
||||
if type == 'float':
|
||||
return FLOAT_SCHEMA(value)
|
||||
raise vol.Invalid("type must either be binary or float, not {}!".format(type))
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = validate_custom_output
|
||||
|
||||
|
||||
def to_code(config):
|
||||
type = config[CONF_TYPE]
|
||||
if type == 'binary':
|
||||
ret_type = output.BinaryOutputPtr
|
||||
klass = CustomBinaryOutputConstructor
|
||||
else:
|
||||
ret_type = output.FloatOutputPtr
|
||||
klass = CustomFloatOutputConstructor
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=std_vector.template(ret_type)):
|
||||
yield
|
||||
|
||||
rhs = klass(template_)
|
||||
custom = variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_OUTPUTS]):
|
||||
output.register_output(custom.get_output(i), conf)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_CUSTOM_OUTPUT'
|
||||
42
esphome/components/output/esp8266_pwm.py
Normal file
42
esphome/components/output/esp8266_pwm.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN, ESP_PLATFORM_ESP8266
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP8266]
|
||||
|
||||
|
||||
def valid_pwm_pin(value):
|
||||
num = value[CONF_NUMBER]
|
||||
cv.one_of(0, 1, 2, 3, 4, 5, 9, 10, 12, 13, 14, 15, 16)(num)
|
||||
return value
|
||||
|
||||
|
||||
ESP8266PWMOutput = output.output_ns.class_('ESP8266PWMOutput', output.FloatOutput, Component)
|
||||
|
||||
PLATFORM_SCHEMA = output.FLOAT_OUTPUT_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(ESP8266PWMOutput),
|
||||
vol.Required(CONF_PIN): vol.All(pins.internal_gpio_output_pin_schema, valid_pwm_pin),
|
||||
vol.Optional(CONF_FREQUENCY): vol.All(cv.frequency, vol.Range(min=1.0e-6)),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_output_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_esp8266_pwm_output(pin)
|
||||
gpio = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
add(gpio.set_frequency(config[CONF_FREQUENCY]))
|
||||
|
||||
output.setup_output_platform(gpio, config)
|
||||
setup_component(gpio, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP8266_PWM_OUTPUT'
|
||||
29
esphome/components/output/gpio.py
Normal file
29
esphome/components/output/gpio.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
GPIOBinaryOutputComponent = output.output_ns.class_('GPIOBinaryOutputComponent',
|
||||
output.BinaryOutput, Component)
|
||||
|
||||
PLATFORM_SCHEMA = output.BINARY_OUTPUT_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(GPIOBinaryOutputComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_output_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_gpio_output(pin)
|
||||
gpio = Pvariable(config[CONF_ID], rhs)
|
||||
output.setup_output_platform(gpio, config)
|
||||
setup_component(gpio, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_GPIO_OUTPUT'
|
||||
47
esphome/components/output/ledc.py
Normal file
47
esphome/components/output/ledc.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import APB_CLOCK_FREQ, CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \
|
||||
CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
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 {}Hz'.format(bit_depth, max_freq))
|
||||
return obj
|
||||
|
||||
|
||||
LEDCOutputComponent = output.output_ns.class_('LEDCOutputComponent', output.FloatOutput, Component)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(output.FLOAT_OUTPUT_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(LEDCOutputComponent),
|
||||
vol.Required(CONF_PIN): pins.output_pin,
|
||||
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))
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), 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(config[CONF_ID], rhs)
|
||||
if CONF_CHANNEL in config:
|
||||
add(ledc.set_channel(config[CONF_CHANNEL]))
|
||||
output.setup_output_platform(ledc, config)
|
||||
setup_component(ledc, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_LEDC_OUTPUT'
|
||||
36
esphome/components/output/my9231.py
Normal file
36
esphome/components/output/my9231.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import output
|
||||
from esphome.components.my9231 import MY9231OutputComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MY9231_ID, CONF_POWER_SUPPLY
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
|
||||
DEPENDENCIES = ['my9231']
|
||||
|
||||
Channel = MY9231OutputComponent.class_('Channel', output.FloatOutput)
|
||||
|
||||
PLATFORM_SCHEMA = output.FLOAT_OUTPUT_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(Channel),
|
||||
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int),
|
||||
vol.Range(min=0, max=65535)),
|
||||
cv.GenerateID(CONF_MY9231_ID): cv.use_variable_id(MY9231OutputComponent),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
power_supply = None
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
my9231 = None
|
||||
for my9231 in get_variable(config[CONF_MY9231_ID]):
|
||||
yield
|
||||
rhs = my9231.create_channel(config[CONF_CHANNEL], power_supply)
|
||||
out = Pvariable(config[CONF_ID], rhs)
|
||||
output.setup_output_platform(out, config, skip_power_supply=True)
|
||||
setup_component(out, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_MY9231_OUTPUT'
|
||||
33
esphome/components/output/pca9685.py
Normal file
33
esphome/components/output/pca9685.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import output
|
||||
from esphome.components.pca9685 import PCA9685OutputComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_PCA9685_ID, CONF_POWER_SUPPLY
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
|
||||
DEPENDENCIES = ['pca9685']
|
||||
|
||||
Channel = PCA9685OutputComponent.class_('Channel', output.FloatOutput)
|
||||
|
||||
PLATFORM_SCHEMA = output.FLOAT_OUTPUT_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(Channel),
|
||||
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int),
|
||||
vol.Range(min=0, max=15)),
|
||||
cv.GenerateID(CONF_PCA9685_ID): cv.use_variable_id(PCA9685OutputComponent),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
power_supply = None
|
||||
if CONF_POWER_SUPPLY in config:
|
||||
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
|
||||
yield
|
||||
for pca9685 in get_variable(config[CONF_PCA9685_ID]):
|
||||
yield
|
||||
rhs = pca9685.create_channel(config[CONF_CHANNEL], power_supply)
|
||||
out = Pvariable(config[CONF_ID], rhs)
|
||||
output.setup_output_platform(out, config, skip_power_supply=True)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_PCA9685_OUTPUT'
|
||||
32
esphome/components/pca9685.py
Normal file
32
esphome/components/pca9685.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_FREQUENCY, CONF_ID
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
MULTI_CONF = True
|
||||
|
||||
PCA9685OutputComponent = output.output_ns.class_('PCA9685OutputComponent',
|
||||
Component, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(PCA9685OutputComponent),
|
||||
vol.Required(CONF_FREQUENCY): vol.All(cv.frequency,
|
||||
vol.Range(min=23.84, max=1525.88)),
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_pca9685_component(config.get(CONF_FREQUENCY))
|
||||
pca9685 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_ADDRESS in config:
|
||||
add(pca9685.set_address(config[CONF_ADDRESS]))
|
||||
setup_component(pca9685, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_PCA9685_OUTPUT'
|
||||
36
esphome/components/pcf8574.py
Normal file
36
esphome/components/pcf8574.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_PCF8575
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, GPIOInputPin, GPIOOutputPin, io_ns
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
MULTI_CONF = True
|
||||
|
||||
PCF8574GPIOMode = io_ns.enum('PCF8574GPIOMode')
|
||||
PCF8675_GPIO_MODES = {
|
||||
'INPUT': PCF8574GPIOMode.PCF8574_INPUT,
|
||||
'INPUT_PULLUP': PCF8574GPIOMode.PCF8574_INPUT_PULLUP,
|
||||
'OUTPUT': PCF8574GPIOMode.PCF8574_OUTPUT,
|
||||
}
|
||||
|
||||
PCF8574GPIOInputPin = io_ns.class_('PCF8574GPIOInputPin', GPIOInputPin)
|
||||
PCF8574GPIOOutputPin = io_ns.class_('PCF8574GPIOOutputPin', GPIOOutputPin)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(pins.PCF8574Component),
|
||||
vol.Optional(CONF_ADDRESS, default=0x21): cv.i2c_address,
|
||||
vol.Optional(CONF_PCF8575, default=False): cv.boolean,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_pcf8574_component(config[CONF_ADDRESS], config[CONF_PCF8575])
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
setup_component(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_PCF8574'
|
||||
46
esphome/components/pn532.py
Normal file
46
esphome/components/pn532.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation, pins
|
||||
from esphome.components import binary_sensor, spi
|
||||
from esphome.components.spi import SPIComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CS_PIN, CONF_ID, CONF_ON_TAG, CONF_SPI_ID, CONF_TRIGGER_ID, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent, Trigger, std_string
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
MULTI_CONF = True
|
||||
|
||||
PN532Component = binary_sensor.binary_sensor_ns.class_('PN532Component', PollingComponent,
|
||||
spi.SPIDevice)
|
||||
PN532Trigger = binary_sensor.binary_sensor_ns.class_('PN532Trigger', Trigger.template(std_string))
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(PN532Component),
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
|
||||
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
vol.Optional(CONF_ON_TAG): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PN532Trigger),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for spi_ in get_variable(config[CONF_SPI_ID]):
|
||||
yield
|
||||
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
|
||||
yield
|
||||
rhs = App.make_pn532_component(spi_, cs, config.get(CONF_UPDATE_INTERVAL))
|
||||
pn532 = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
for conf_ in config.get(CONF_ON_TAG, []):
|
||||
trigger = Pvariable(conf_[CONF_TRIGGER_ID], pn532.make_trigger())
|
||||
automation.build_automation(trigger, std_string, conf_)
|
||||
|
||||
setup_component(pn532, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_PN532'
|
||||
36
esphome/components/power_supply.py
Normal file
36
esphome/components/power_supply.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
|
||||
PowerSupplyComponent = esphome_ns.class_('PowerSupplyComponent', Component)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(PowerSupplyComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_ENABLE_TIME): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_KEEP_ON_TIME): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_output_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
|
||||
rhs = App.make_power_supply(pin)
|
||||
psu = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_ENABLE_TIME in config:
|
||||
add(psu.set_enable_time(config[CONF_ENABLE_TIME]))
|
||||
if CONF_KEEP_ON_TIME in config:
|
||||
add(psu.set_keep_on_time(config[CONF_KEEP_ON_TIME]))
|
||||
|
||||
setup_component(psu, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_OUTPUT'
|
||||
29
esphome/components/rdm6300.py
Normal file
29
esphome/components/rdm6300.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_UART_ID
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
RDM6300Component = binary_sensor.binary_sensor_ns.class_('RDM6300Component', Component,
|
||||
uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(RDM6300Component),
|
||||
cv.GenerateID(CONF_UART_ID): cv.use_variable_id(uart.UARTComponent),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for uart_ in get_variable(config[CONF_UART_ID]):
|
||||
yield
|
||||
rhs = App.make_rdm6300_component(uart_)
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
setup_component(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_RDM6300'
|
||||
74
esphome/components/remote_receiver.py
Normal file
74
esphome/components/remote_receiver.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BUFFER_SIZE, CONF_DUMP, CONF_FILTER, CONF_ID, CONF_IDLE, \
|
||||
CONF_PIN, CONF_TOLERANCE
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component, esphome_ns
|
||||
from esphome.py_compat import string_types
|
||||
|
||||
remote_ns = esphome_ns.namespace('remote')
|
||||
MULTI_CONF = True
|
||||
|
||||
RemoteControlComponentBase = remote_ns.class_('RemoteControlComponentBase')
|
||||
RemoteReceiverComponent = remote_ns.class_('RemoteReceiverComponent',
|
||||
RemoteControlComponentBase,
|
||||
Component)
|
||||
|
||||
RemoteReceiveDumper = remote_ns.class_('RemoteReceiveDumper')
|
||||
|
||||
DUMPERS = {
|
||||
'jvc': remote_ns.class_('JVCDumper', RemoteReceiveDumper),
|
||||
'lg': remote_ns.class_('LGDumper', RemoteReceiveDumper),
|
||||
'nec': remote_ns.class_('NECDumper', RemoteReceiveDumper),
|
||||
'panasonic': remote_ns.class_('PanasonicDumper', RemoteReceiveDumper),
|
||||
'raw': remote_ns.class_('RawDumper', RemoteReceiveDumper),
|
||||
'samsung': remote_ns.class_('SamsungDumper', RemoteReceiveDumper),
|
||||
'sony': remote_ns.class_('SonyDumper', RemoteReceiveDumper),
|
||||
'rc_switch': remote_ns.class_('RCSwitchDumper', RemoteReceiveDumper),
|
||||
}
|
||||
|
||||
|
||||
def validate_dumpers_all(value):
|
||||
if not isinstance(value, string_types):
|
||||
raise vol.Invalid("Not valid dumpers")
|
||||
if value.upper() == "ALL":
|
||||
return list(sorted(list(DUMPERS)))
|
||||
raise vol.Invalid("Not valid dumpers")
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(RemoteReceiverComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||
vol.Optional(CONF_DUMP, default=[]):
|
||||
vol.Any(validate_dumpers_all, cv.ensure_list(cv.one_of(*DUMPERS, lower=True))),
|
||||
vol.Optional(CONF_TOLERANCE): vol.All(cv.percentage_int, vol.Range(min=0)),
|
||||
vol.Optional(CONF_BUFFER_SIZE): cv.validate_bytes,
|
||||
vol.Optional(CONF_FILTER): cv.positive_time_period_microseconds,
|
||||
vol.Optional(CONF_IDLE): cv.positive_time_period_microseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_input_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_remote_receiver_component(pin)
|
||||
receiver = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
for dumper in config[CONF_DUMP]:
|
||||
add(receiver.add_dumper(DUMPERS[dumper].new()))
|
||||
if CONF_TOLERANCE in config:
|
||||
add(receiver.set_tolerance(config[CONF_TOLERANCE]))
|
||||
if CONF_BUFFER_SIZE in config:
|
||||
add(receiver.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||
if CONF_FILTER in config:
|
||||
add(receiver.set_filter_us(config[CONF_FILTER]))
|
||||
if CONF_IDLE in config:
|
||||
add(receiver.set_idle_us(config[CONF_IDLE]))
|
||||
|
||||
setup_component(receiver, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_REMOTE_RECEIVER'
|
||||
120
esphome/components/remote_transmitter.py
Normal file
120
esphome/components/remote_transmitter.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components.remote_receiver import RemoteControlComponentBase, remote_ns
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_CARRIER_DUTY_PERCENT, CONF_CHANNEL, CONF_CODE, \
|
||||
CONF_DEVICE, CONF_FAMILY, CONF_GROUP, CONF_ID, CONF_INVERTED, CONF_ONE, CONF_PIN, \
|
||||
CONF_PROTOCOL, CONF_PULSE_LENGTH, CONF_STATE, CONF_SYNC, CONF_ZERO
|
||||
from esphome.core import HexInt
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
from esphome.py_compat import text_type
|
||||
|
||||
RemoteTransmitterComponent = remote_ns.class_('RemoteTransmitterComponent',
|
||||
RemoteControlComponentBase, Component)
|
||||
RCSwitchProtocol = remote_ns.class_('RCSwitchProtocol')
|
||||
rc_switch_protocols = remote_ns.rc_switch_protocols
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
def validate_rc_switch_code(value):
|
||||
if not isinstance(value, (str, text_type)):
|
||||
raise vol.Invalid("All RCSwitch codes must be in quotes ('')")
|
||||
for c in value:
|
||||
if c not in ('0', '1'):
|
||||
raise vol.Invalid(u"Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed"
|
||||
u"".format(c))
|
||||
if len(value) > 32:
|
||||
raise vol.Invalid("Maximum length for RCSwitch codes is 32, code '{}' has length {}"
|
||||
"".format(value, len(value)))
|
||||
if not value:
|
||||
raise vol.Invalid("RCSwitch code must not be empty")
|
||||
return value
|
||||
|
||||
|
||||
RC_SWITCH_TIMING_SCHEMA = vol.All([cv.uint8_t], vol.Length(min=2, max=2))
|
||||
|
||||
RC_SWITCH_PROTOCOL_SCHEMA = vol.Any(
|
||||
vol.All(vol.Coerce(int), vol.Range(min=1, max=7)),
|
||||
vol.Schema({
|
||||
vol.Required(CONF_PULSE_LENGTH): cv.uint32_t,
|
||||
vol.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA,
|
||||
vol.Optional(CONF_ZERO, default=[1, 3]): RC_SWITCH_TIMING_SCHEMA,
|
||||
vol.Optional(CONF_ONE, default=[3, 1]): RC_SWITCH_TIMING_SCHEMA,
|
||||
vol.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
})
|
||||
)
|
||||
|
||||
RC_SWITCH_RAW_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_CODE): validate_rc_switch_code,
|
||||
vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
|
||||
})
|
||||
RC_SWITCH_TYPE_A_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_GROUP): vol.All(validate_rc_switch_code, vol.Length(min=5, max=5)),
|
||||
vol.Required(CONF_DEVICE): vol.All(validate_rc_switch_code, vol.Length(min=5, max=5)),
|
||||
vol.Required(CONF_STATE): cv.boolean,
|
||||
vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
|
||||
})
|
||||
RC_SWITCH_TYPE_B_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_ADDRESS): vol.All(cv.uint8_t, vol.Range(min=1, max=4)),
|
||||
vol.Required(CONF_CHANNEL): vol.All(cv.uint8_t, vol.Range(min=1, max=4)),
|
||||
vol.Required(CONF_STATE): cv.boolean,
|
||||
vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
|
||||
})
|
||||
RC_SWITCH_TYPE_C_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_FAMILY): cv.one_of('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
|
||||
'l', 'm', 'n', 'o', 'p', lower=True),
|
||||
vol.Required(CONF_GROUP): vol.All(cv.uint8_t, vol.Range(min=1, max=4)),
|
||||
vol.Required(CONF_DEVICE): vol.All(cv.uint8_t, vol.Range(min=1, max=4)),
|
||||
vol.Required(CONF_STATE): cv.boolean,
|
||||
vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
|
||||
})
|
||||
RC_SWITCH_TYPE_D_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_GROUP): cv.one_of('a', 'b', 'c', 'd', lower=True),
|
||||
vol.Required(CONF_DEVICE): vol.All(cv.uint8_t, vol.Range(min=1, max=3)),
|
||||
vol.Required(CONF_STATE): cv.boolean,
|
||||
vol.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(RemoteTransmitterComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Optional(CONF_CARRIER_DUTY_PERCENT): vol.All(cv.percentage_int,
|
||||
vol.Range(min=1, max=100)),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def build_rc_switch_protocol(config):
|
||||
if isinstance(config, int):
|
||||
return rc_switch_protocols[config]
|
||||
pl = config[CONF_PULSE_LENGTH]
|
||||
return RCSwitchProtocol(config[CONF_SYNC][0] * pl, config[CONF_SYNC][1] * pl,
|
||||
config[CONF_ZERO][0] * pl, config[CONF_ZERO][1] * pl,
|
||||
config[CONF_ONE][0] * pl, config[CONF_ONE][1] * pl,
|
||||
config[CONF_INVERTED])
|
||||
|
||||
|
||||
def binary_code(value):
|
||||
code = 0
|
||||
for val in value:
|
||||
code <<= 1
|
||||
code |= val == '1'
|
||||
return HexInt(code)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_output_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_remote_transmitter_component(pin)
|
||||
transmitter = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_CARRIER_DUTY_PERCENT in config:
|
||||
add(transmitter.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT]))
|
||||
|
||||
setup_component(transmitter, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_REMOTE_TRANSMITTER'
|
||||
52
esphome/components/script.py
Normal file
52
esphome/components/script.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
from esphome.cpp_types import Action, NoArg, Trigger, esphome_ns
|
||||
|
||||
Script = esphome_ns.class_('Script', Trigger.template(NoArg))
|
||||
ScriptExecuteAction = esphome_ns.class_('ScriptExecuteAction', Action)
|
||||
ScriptStopAction = esphome_ns.class_('ScriptStopAction', Action)
|
||||
|
||||
CONFIG_SCHEMA = automation.validate_automation({
|
||||
vol.Required(CONF_ID): cv.declare_variable_id(Script),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for conf in config:
|
||||
trigger = Pvariable(conf[CONF_ID], Script.new())
|
||||
automation.build_automation(trigger, NoArg, conf)
|
||||
|
||||
|
||||
CONF_SCRIPT_EXECUTE = 'script.execute'
|
||||
SCRIPT_EXECUTE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Script),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_SCRIPT_EXECUTE, SCRIPT_EXECUTE_ACTION_SCHEMA)
|
||||
def script_execute_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_execute_action(template_arg)
|
||||
type = ScriptExecuteAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_SCRIPT_STOP = 'script.stop'
|
||||
SCRIPT_STOP_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Script),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_SCRIPT_STOP, SCRIPT_STOP_ACTION_SCHEMA)
|
||||
def script_stop_action_to_code(config, action_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_stop_action(template_arg)
|
||||
type = ScriptStopAction.template(arg_type)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
262
esphome/components/sensor/__init__.py
Normal file
262
esphome/components/sensor/__init__.py
Normal file
@@ -0,0 +1,262 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import CONDITION_REGISTRY
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, \
|
||||
CONF_DEBOUNCE, CONF_DELTA, CONF_EXPIRE_AFTER, CONF_EXPONENTIAL_MOVING_AVERAGE, CONF_FILTERS, \
|
||||
CONF_FILTER_NAN, CONF_FILTER_OUT, CONF_HEARTBEAT, CONF_ICON, CONF_ID, CONF_INTERNAL, \
|
||||
CONF_LAMBDA, CONF_MQTT_ID, CONF_MULTIPLY, CONF_OFFSET, CONF_ON_RAW_VALUE, CONF_ON_VALUE, \
|
||||
CONF_ON_VALUE_RANGE, CONF_OR, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, \
|
||||
CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_THROTTLE, CONF_TRIGGER_ID, CONF_UNIQUE, \
|
||||
CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_types import App, Component, Nameable, PollingComponent, Trigger, \
|
||||
esphome_ns, float_, optional
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
|
||||
|
||||
def validate_recursive_filter(value):
|
||||
return FILTERS_SCHEMA(value)
|
||||
|
||||
|
||||
def validate_send_first_at(value):
|
||||
send_first_at = value.get(CONF_SEND_FIRST_AT)
|
||||
send_every = value[CONF_SEND_EVERY]
|
||||
if send_first_at is not None and send_first_at > send_every:
|
||||
raise vol.Invalid("send_first_at must be smaller than or equal to send_every! {} <= {}"
|
||||
"".format(send_first_at, send_every))
|
||||
return value
|
||||
|
||||
|
||||
FILTER_KEYS = [CONF_OFFSET, CONF_MULTIPLY, CONF_FILTER_OUT, CONF_FILTER_NAN,
|
||||
CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_EXPONENTIAL_MOVING_AVERAGE, CONF_LAMBDA,
|
||||
CONF_THROTTLE, CONF_DELTA, CONF_UNIQUE, CONF_HEARTBEAT, CONF_DEBOUNCE, CONF_OR]
|
||||
|
||||
FILTERS_SCHEMA = cv.ensure_list({
|
||||
vol.Optional(CONF_OFFSET): cv.float_,
|
||||
vol.Optional(CONF_MULTIPLY): cv.float_,
|
||||
vol.Optional(CONF_FILTER_OUT): cv.float_,
|
||||
vol.Optional(CONF_FILTER_NAN): None,
|
||||
vol.Optional(CONF_SLIDING_WINDOW_MOVING_AVERAGE): vol.All(vol.Schema({
|
||||
vol.Optional(CONF_WINDOW_SIZE, default=15): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int,
|
||||
vol.Optional(CONF_SEND_FIRST_AT): cv.positive_not_null_int,
|
||||
}), validate_send_first_at),
|
||||
vol.Optional(CONF_EXPONENTIAL_MOVING_AVERAGE): vol.Schema({
|
||||
vol.Optional(CONF_ALPHA, default=0.1): cv.positive_float,
|
||||
vol.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int,
|
||||
}),
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_DELTA): cv.float_,
|
||||
vol.Optional(CONF_UNIQUE): None,
|
||||
vol.Optional(CONF_HEARTBEAT): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_DEBOUNCE): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_OR): validate_recursive_filter,
|
||||
}, cv.has_exactly_one_key(*FILTER_KEYS))
|
||||
|
||||
# Base
|
||||
sensor_ns = esphome_ns.namespace('sensor')
|
||||
Sensor = sensor_ns.class_('Sensor', Nameable)
|
||||
SensorPtr = Sensor.operator('ptr')
|
||||
MQTTSensorComponent = sensor_ns.class_('MQTTSensorComponent', mqtt.MQTTComponent)
|
||||
|
||||
PollingSensorComponent = sensor_ns.class_('PollingSensorComponent', PollingComponent, Sensor)
|
||||
EmptySensor = sensor_ns.class_('EmptySensor', Sensor)
|
||||
EmptyPollingParentSensor = sensor_ns.class_('EmptyPollingParentSensor', EmptySensor)
|
||||
|
||||
# Triggers
|
||||
SensorStateTrigger = sensor_ns.class_('SensorStateTrigger', Trigger.template(float_))
|
||||
SensorRawStateTrigger = sensor_ns.class_('SensorRawStateTrigger', Trigger.template(float_))
|
||||
ValueRangeTrigger = sensor_ns.class_('ValueRangeTrigger', Trigger.template(float_), Component)
|
||||
|
||||
# Filters
|
||||
Filter = sensor_ns.class_('Filter')
|
||||
SlidingWindowMovingAverageFilter = sensor_ns.class_('SlidingWindowMovingAverageFilter', Filter)
|
||||
ExponentialMovingAverageFilter = sensor_ns.class_('ExponentialMovingAverageFilter', Filter)
|
||||
LambdaFilter = sensor_ns.class_('LambdaFilter', Filter)
|
||||
OffsetFilter = sensor_ns.class_('OffsetFilter', Filter)
|
||||
MultiplyFilter = sensor_ns.class_('MultiplyFilter', Filter)
|
||||
FilterOutValueFilter = sensor_ns.class_('FilterOutValueFilter', Filter)
|
||||
FilterOutNANFilter = sensor_ns.class_('FilterOutNANFilter', Filter)
|
||||
ThrottleFilter = sensor_ns.class_('ThrottleFilter', Filter)
|
||||
DebounceFilter = sensor_ns.class_('DebounceFilter', Filter, Component)
|
||||
HeartbeatFilter = sensor_ns.class_('HeartbeatFilter', Filter, Component)
|
||||
DeltaFilter = sensor_ns.class_('DeltaFilter', Filter)
|
||||
OrFilter = sensor_ns.class_('OrFilter', Filter)
|
||||
UniqueFilter = sensor_ns.class_('UniqueFilter', Filter)
|
||||
SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter)
|
||||
|
||||
SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTSensorComponent),
|
||||
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.All(cv.requires_component('mqtt'),
|
||||
vol.Any(None, cv.positive_time_period_milliseconds)),
|
||||
vol.Optional(CONF_FILTERS): FILTERS_SCHEMA,
|
||||
vol.Optional(CONF_ON_VALUE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(SensorStateTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_RAW_VALUE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(SensorRawStateTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ValueRangeTrigger),
|
||||
vol.Optional(CONF_ABOVE): cv.float_,
|
||||
vol.Optional(CONF_BELOW): cv.float_,
|
||||
}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)),
|
||||
})
|
||||
|
||||
SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(SENSOR_SCHEMA.schema)
|
||||
|
||||
|
||||
def setup_filter(config):
|
||||
if CONF_OFFSET in config:
|
||||
yield OffsetFilter.new(config[CONF_OFFSET])
|
||||
elif CONF_MULTIPLY in config:
|
||||
yield MultiplyFilter.new(config[CONF_MULTIPLY])
|
||||
elif CONF_FILTER_OUT in config:
|
||||
yield FilterOutValueFilter.new(config[CONF_FILTER_OUT])
|
||||
elif CONF_FILTER_NAN in config:
|
||||
yield FilterOutNANFilter.new()
|
||||
elif CONF_SLIDING_WINDOW_MOVING_AVERAGE in config:
|
||||
conf = config[CONF_SLIDING_WINDOW_MOVING_AVERAGE]
|
||||
yield SlidingWindowMovingAverageFilter.new(conf[CONF_WINDOW_SIZE], conf[CONF_SEND_EVERY],
|
||||
conf.get(CONF_SEND_FIRST_AT))
|
||||
elif CONF_EXPONENTIAL_MOVING_AVERAGE in config:
|
||||
conf = config[CONF_EXPONENTIAL_MOVING_AVERAGE]
|
||||
yield ExponentialMovingAverageFilter.new(conf[CONF_ALPHA], conf[CONF_SEND_EVERY])
|
||||
elif CONF_LAMBDA in config:
|
||||
for lambda_ in process_lambda(config[CONF_LAMBDA], [(float_, 'x')],
|
||||
return_type=optional.template(float_)):
|
||||
yield None
|
||||
yield LambdaFilter.new(lambda_)
|
||||
elif CONF_THROTTLE in config:
|
||||
yield ThrottleFilter.new(config[CONF_THROTTLE])
|
||||
elif CONF_DELTA in config:
|
||||
yield DeltaFilter.new(config[CONF_DELTA])
|
||||
elif CONF_OR in config:
|
||||
for filters in setup_filters(config[CONF_OR]):
|
||||
yield None
|
||||
yield OrFilter.new(filters)
|
||||
elif CONF_HEARTBEAT in config:
|
||||
yield App.register_component(HeartbeatFilter.new(config[CONF_HEARTBEAT]))
|
||||
elif CONF_DEBOUNCE in config:
|
||||
yield App.register_component(DebounceFilter.new(config[CONF_DEBOUNCE]))
|
||||
elif CONF_UNIQUE in config:
|
||||
yield UniqueFilter.new()
|
||||
|
||||
|
||||
def setup_filters(config):
|
||||
filters = []
|
||||
for conf in config:
|
||||
for filter in setup_filter(conf):
|
||||
yield None
|
||||
filters.append(filter)
|
||||
yield filters
|
||||
|
||||
|
||||
def setup_sensor_core_(sensor_var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(sensor_var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_UNIT_OF_MEASUREMENT in config:
|
||||
add(sensor_var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
|
||||
if CONF_ICON in config:
|
||||
add(sensor_var.set_icon(config[CONF_ICON]))
|
||||
if CONF_ACCURACY_DECIMALS in config:
|
||||
add(sensor_var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
||||
if CONF_FILTERS in config:
|
||||
for filters in setup_filters(config[CONF_FILTERS]):
|
||||
yield
|
||||
add(sensor_var.set_filters(filters))
|
||||
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
rhs = sensor_var.make_state_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, float_, conf)
|
||||
for conf in config.get(CONF_ON_RAW_VALUE, []):
|
||||
rhs = sensor_var.make_raw_state_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
automation.build_automation(trigger, float_, conf)
|
||||
for conf in config.get(CONF_ON_VALUE_RANGE, []):
|
||||
rhs = sensor_var.make_value_range_trigger()
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
|
||||
add(App.register_component(trigger))
|
||||
if CONF_ABOVE in conf:
|
||||
for template_ in templatable(conf[CONF_ABOVE], float_, float_):
|
||||
yield
|
||||
add(trigger.set_min(template_))
|
||||
if CONF_BELOW in conf:
|
||||
for template_ in templatable(conf[CONF_BELOW], float_, float_):
|
||||
yield
|
||||
add(trigger.set_max(template_))
|
||||
automation.build_automation(trigger, float_, conf)
|
||||
|
||||
mqtt_ = sensor_var.Pget_mqtt()
|
||||
if CONF_EXPIRE_AFTER in config:
|
||||
if config[CONF_EXPIRE_AFTER] is None:
|
||||
add(mqtt_.disable_expire_after())
|
||||
else:
|
||||
add(mqtt_.set_expire_after(config[CONF_EXPIRE_AFTER]))
|
||||
setup_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
def setup_sensor(sensor_obj, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
sensor_obj = Pvariable(config[CONF_ID], sensor_obj, has_side_effects=True)
|
||||
CORE.add_job(setup_sensor_core_, sensor_obj, config)
|
||||
|
||||
|
||||
def register_sensor(var, config):
|
||||
sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True)
|
||||
add(App.register_sensor(sensor_var))
|
||||
CORE.add_job(setup_sensor_core_, sensor_var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_SENSOR'
|
||||
|
||||
CONF_SENSOR_IN_RANGE = 'sensor.in_range'
|
||||
SENSOR_IN_RANGE_CONDITION_SCHEMA = vol.All({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Sensor),
|
||||
vol.Optional(CONF_ABOVE): cv.float_,
|
||||
vol.Optional(CONF_BELOW): cv.float_,
|
||||
}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_SENSOR_IN_RANGE, SENSOR_IN_RANGE_CONDITION_SCHEMA)
|
||||
def sensor_in_range_to_code(config, condition_id, arg_type, template_arg):
|
||||
for var in get_variable(config[CONF_ID]):
|
||||
yield None
|
||||
rhs = var.make_sensor_in_range_condition(template_arg)
|
||||
type = SensorInRangeCondition.template(arg_type)
|
||||
cond = Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
if CONF_ABOVE in config:
|
||||
add(cond.set_min(config[CONF_ABOVE]))
|
||||
if CONF_BELOW in config:
|
||||
add(cond.set_max(config[CONF_BELOW]))
|
||||
|
||||
yield cond
|
||||
|
||||
|
||||
def core_to_hass_config(data, config):
|
||||
ret = mqtt.build_hass_config(data, 'sensor', config, include_state=True, include_command=False)
|
||||
if ret is None:
|
||||
return None
|
||||
if CONF_UNIT_OF_MEASUREMENT in config:
|
||||
ret['unit_of_measurement'] = config[CONF_UNIT_OF_MEASUREMENT]
|
||||
if CONF_EXPIRE_AFTER in config:
|
||||
expire = config[CONF_EXPIRE_AFTER]
|
||||
if expire is not None:
|
||||
ret['expire_after'] = expire.total_seconds
|
||||
if CONF_ICON in config:
|
||||
ret['icon'] = config[CONF_ICON]
|
||||
return ret
|
||||
66
esphome/components/sensor/adc.py
Normal file
66
esphome/components/sensor/adc.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_NAME, CONF_PIN, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, global_ns
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
'0db': global_ns.ADC_0db,
|
||||
'2.5db': global_ns.ADC_2_5db,
|
||||
'6db': global_ns.ADC_6db,
|
||||
'11db': global_ns.ADC_11db,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == 'VCC':
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
|
||||
|
||||
ADCSensorComponent = sensor.sensor_ns.class_('ADCSensorComponent', sensor.PollingSensorComponent)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ADCSensorComponent),
|
||||
vol.Required(CONF_PIN): validate_adc_pin,
|
||||
vol.Optional(CONF_ATTENUATION): vol.All(cv.only_on_esp32, cv.one_of(*ATTENUATION_MODES,
|
||||
lower=True)),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
pin = config[CONF_PIN]
|
||||
if pin == 'VCC':
|
||||
pin = 0
|
||||
rhs = App.make_adc_sensor(config[CONF_NAME], pin,
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
adc = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_ATTENUATION in config:
|
||||
add(adc.set_attenuation(ATTENUATION_MODES[config[CONF_ATTENUATION]]))
|
||||
sensor.setup_sensor(adc, config)
|
||||
setup_component(adc, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ADC_SENSOR'
|
||||
|
||||
|
||||
def required_build_flags(config):
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
return '-DUSE_ADC_SENSOR_VCC'
|
||||
return None
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
|
||||
|
||||
def includes(config):
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
return 'ADC_MODE(ADC_VCC);'
|
||||
return None
|
||||
76
esphome/components/sensor/ads1115.py
Normal file
76
esphome/components/sensor/ads1115.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
from esphome.components.ads1115 import ADS1115Component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADS1115_ID, CONF_GAIN, CONF_MULTIPLEXER, CONF_NAME, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import get_variable
|
||||
from esphome.py_compat import string_types
|
||||
|
||||
DEPENDENCIES = ['ads1115']
|
||||
|
||||
ADS1115Multiplexer = sensor.sensor_ns.enum('ADS1115Multiplexer')
|
||||
MUX = {
|
||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = sensor.sensor_ns.enum('ADS1115Gain')
|
||||
GAIN = {
|
||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
'4.096': ADS1115Gain.ADS1115_GAIN_6P096,
|
||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
value = u'{:0.03f}'.format(value)
|
||||
elif not isinstance(value, string_types):
|
||||
raise vol.Invalid('invalid gain "{}"'.format(value))
|
||||
|
||||
return cv.one_of(*GAIN)(value)
|
||||
|
||||
|
||||
def validate_mux(value):
|
||||
value = cv.string(value).upper()
|
||||
value = value.replace(' ', '_')
|
||||
return cv.one_of(*MUX)(value)
|
||||
|
||||
|
||||
ADS1115Sensor = sensor.sensor_ns.class_('ADS1115Sensor', sensor.EmptySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ADS1115Sensor),
|
||||
vol.Required(CONF_MULTIPLEXER): validate_mux,
|
||||
vol.Required(CONF_GAIN): validate_gain,
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_variable_id(ADS1115Component),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_ADS1115_ID]):
|
||||
yield
|
||||
|
||||
mux = MUX[config[CONF_MULTIPLEXER]]
|
||||
gain = GAIN[config[CONF_GAIN]]
|
||||
rhs = hub.get_sensor(config[CONF_NAME], mux, gain, config.get(CONF_UPDATE_INTERVAL))
|
||||
sensor.register_sensor(rhs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ADS1115_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
35
esphome/components/sensor/apds9960.py
Normal file
35
esphome/components/sensor/apds9960.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
from esphome.components.apds9960 import APDS9960, CONF_APDS9960_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_TYPE
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
|
||||
TYPES = {
|
||||
'CLEAR': 'make_clear_channel',
|
||||
'RED': 'make_red_channel',
|
||||
'GREEN': 'make_green_channel',
|
||||
'BLUE': 'make_blue_channel',
|
||||
'PROXIMITY': 'make_proximity',
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(sensor.Sensor),
|
||||
vol.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_APDS9960_ID]):
|
||||
yield
|
||||
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
||||
rhs = func(config[CONF_NAME])
|
||||
sensor.register_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
45
esphome/components/sensor/bh1750.py
Normal file
45
esphome/components/sensor/bh1750.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_RESOLUTION, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
BH1750Resolution = sensor.sensor_ns.enum('BH1750Resolution')
|
||||
BH1750_RESOLUTIONS = {
|
||||
4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
|
||||
1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX,
|
||||
0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX,
|
||||
}
|
||||
|
||||
BH1750Sensor = sensor.sensor_ns.class_('BH1750Sensor', sensor.PollingSensorComponent,
|
||||
i2c.I2CDevice)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BH1750Sensor),
|
||||
vol.Optional(CONF_ADDRESS, default=0x23): cv.i2c_address,
|
||||
vol.Optional(CONF_RESOLUTION): vol.All(cv.positive_float, cv.one_of(*BH1750_RESOLUTIONS)),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_bh1750_sensor(config[CONF_NAME], config[CONF_ADDRESS],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
bh1750 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_RESOLUTION in config:
|
||||
add(bh1750.set_resolution(BH1750_RESOLUTIONS[config[CONF_RESOLUTION]]))
|
||||
sensor.setup_sensor(bh1750, config)
|
||||
setup_component(bh1750, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BH1750'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
30
esphome/components/sensor/ble_rssi.py
Normal file
30
esphome/components/sensor/ble_rssi.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESP32BLETracker, \
|
||||
make_address_array
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_NAME
|
||||
from esphome.cpp_generator import get_variable
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ESP32BLERSSISensor = esphome_ns.class_('ESP32BLERSSISensor', sensor.Sensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32BLERSSISensor),
|
||||
vol.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_variable_id(ESP32BLETracker)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_ESP32_BLE_ID]):
|
||||
yield
|
||||
rhs = hub.make_rssi_sensor(config[CONF_NAME], make_address_array(config[CONF_MAC_ADDRESS]))
|
||||
sensor.register_sensor(rhs, config)
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
93
esphome/components/sensor/bme280.py
Normal file
93
esphome/components/sensor/bme280.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_NAME, \
|
||||
CONF_OVERSAMPLING, CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
BME280Oversampling = sensor.sensor_ns.enum('BME280Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||
'1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||
'2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||
'4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||
'8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||
'16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME280IIRFilter = sensor.sensor_ns.enum('BME280IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||
'2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||
'4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||
'8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||
'16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BME280_OVERSAMPLING_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||
vol.Optional(CONF_OVERSAMPLING): cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})
|
||||
|
||||
BME280Component = sensor.sensor_ns.class_('BME280Component', PollingComponent, i2c.I2CDevice)
|
||||
BME280TemperatureSensor = sensor.sensor_ns.class_('BME280TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BME280PressureSensor = sensor.sensor_ns.class_('BME280PressureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BME280HumiditySensor = sensor.sensor_ns.class_('BME280HumiditySensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME280Component),
|
||||
vol.Optional(CONF_ADDRESS, default=0x77): cv.i2c_address,
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(BME280_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME280TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_PRESSURE): cv.nameable(BME280_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME280PressureSensor),
|
||||
})),
|
||||
vol.Required(CONF_HUMIDITY): cv.nameable(BME280_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME280HumiditySensor),
|
||||
})),
|
||||
vol.Optional(CONF_IIR_FILTER): cv.one_of(*IIR_FILTER_OPTIONS, upper=True),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_bme280_sensor(config[CONF_TEMPERATURE][CONF_NAME],
|
||||
config[CONF_PRESSURE][CONF_NAME],
|
||||
config[CONF_HUMIDITY][CONF_NAME],
|
||||
config[CONF_ADDRESS],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
bme280 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_OVERSAMPLING in config[CONF_TEMPERATURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_TEMPERATURE][CONF_OVERSAMPLING]]
|
||||
add(bme280.set_temperature_oversampling(constant))
|
||||
if CONF_OVERSAMPLING in config[CONF_PRESSURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_PRESSURE][CONF_OVERSAMPLING]]
|
||||
add(bme280.set_pressure_oversampling(constant))
|
||||
if CONF_OVERSAMPLING in config[CONF_HUMIDITY]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_HUMIDITY][CONF_OVERSAMPLING]]
|
||||
add(bme280.set_humidity_oversampling(constant))
|
||||
if CONF_IIR_FILTER in config:
|
||||
constant = IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]
|
||||
add(bme280.set_iir_filter(constant))
|
||||
|
||||
sensor.setup_sensor(bme280.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(bme280.Pget_pressure_sensor(), config[CONF_PRESSURE])
|
||||
sensor.setup_sensor(bme280.Pget_humidity_sensor(), config[CONF_HUMIDITY])
|
||||
setup_component(bme280, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BME280'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_PRESSURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_HUMIDITY])]
|
||||
117
esphome/components/sensor/bme680.py
Normal file
117
esphome/components/sensor/bme680.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \
|
||||
CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_NAME, CONF_OVERSAMPLING, CONF_PRESSURE, \
|
||||
CONF_TEMPERATURE, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
BME680Oversampling = sensor.sensor_ns.enum('BME680Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
||||
'1X': BME680Oversampling.BME680_OVERSAMPLING_1X,
|
||||
'2X': BME680Oversampling.BME680_OVERSAMPLING_2X,
|
||||
'4X': BME680Oversampling.BME680_OVERSAMPLING_4X,
|
||||
'8X': BME680Oversampling.BME680_OVERSAMPLING_8X,
|
||||
'16X': BME680Oversampling.BME680_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME680IIRFilter = sensor.sensor_ns.enum('BME680IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
||||
'1X': BME680IIRFilter.BME680_IIR_FILTER_1X,
|
||||
'3X': BME680IIRFilter.BME680_IIR_FILTER_3X,
|
||||
'7X': BME680IIRFilter.BME680_IIR_FILTER_7X,
|
||||
'15X': BME680IIRFilter.BME680_IIR_FILTER_15X,
|
||||
'31X': BME680IIRFilter.BME680_IIR_FILTER_31X,
|
||||
'63X': BME680IIRFilter.BME680_IIR_FILTER_63X,
|
||||
'127X': BME680IIRFilter.BME680_IIR_FILTER_127X,
|
||||
}
|
||||
|
||||
BME680_OVERSAMPLING_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||
vol.Optional(CONF_OVERSAMPLING): cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})
|
||||
|
||||
BME680Component = sensor.sensor_ns.class_('BME680Component', PollingComponent, i2c.I2CDevice)
|
||||
BME680TemperatureSensor = sensor.sensor_ns.class_('BME680TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BME680PressureSensor = sensor.sensor_ns.class_('BME680PressureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BME680HumiditySensor = sensor.sensor_ns.class_('BME680HumiditySensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BME680GasResistanceSensor = sensor.sensor_ns.class_('BME680GasResistanceSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME680Component),
|
||||
vol.Optional(CONF_ADDRESS, default=0x76): cv.i2c_address,
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(BME680_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME680TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_PRESSURE): cv.nameable(BME680_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME680PressureSensor),
|
||||
})),
|
||||
vol.Required(CONF_HUMIDITY): cv.nameable(BME680_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME680HumiditySensor),
|
||||
})),
|
||||
vol.Required(CONF_GAS_RESISTANCE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME680GasResistanceSensor),
|
||||
})),
|
||||
vol.Optional(CONF_IIR_FILTER): cv.one_of(*IIR_FILTER_OPTIONS, upper=True),
|
||||
vol.Optional(CONF_HEATER): vol.Any(None, vol.All(vol.Schema({
|
||||
vol.Optional(CONF_TEMPERATURE, default=320): vol.All(vol.Coerce(int), vol.Range(200, 400)),
|
||||
vol.Optional(CONF_DURATION, default='150ms'): vol.All(
|
||||
cv.positive_time_period_milliseconds, vol.Range(max=core.TimePeriod(milliseconds=4032)))
|
||||
}, cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION)))),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_bme680_sensor(config[CONF_TEMPERATURE][CONF_NAME],
|
||||
config[CONF_PRESSURE][CONF_NAME],
|
||||
config[CONF_HUMIDITY][CONF_NAME],
|
||||
config[CONF_GAS_RESISTANCE][CONF_NAME],
|
||||
config[CONF_ADDRESS],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
bme680 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_OVERSAMPLING in config[CONF_TEMPERATURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_TEMPERATURE][CONF_OVERSAMPLING]]
|
||||
add(bme680.set_temperature_oversampling(constant))
|
||||
if CONF_OVERSAMPLING in config[CONF_PRESSURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_PRESSURE][CONF_OVERSAMPLING]]
|
||||
add(bme680.set_pressure_oversampling(constant))
|
||||
if CONF_OVERSAMPLING in config[CONF_HUMIDITY]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_HUMIDITY][CONF_OVERSAMPLING]]
|
||||
add(bme680.set_humidity_oversampling(constant))
|
||||
if CONF_IIR_FILTER in config:
|
||||
constant = IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]
|
||||
add(bme680.set_iir_filter(constant))
|
||||
if CONF_HEATER in config:
|
||||
conf = config[CONF_HEATER]
|
||||
if not conf:
|
||||
add(bme680.set_heater(0, 0))
|
||||
else:
|
||||
add(bme680.set_heater(conf[CONF_TEMPERATURE], conf[CONF_DURATION]))
|
||||
|
||||
sensor.setup_sensor(bme680.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(bme680.Pget_pressure_sensor(), config[CONF_PRESSURE])
|
||||
sensor.setup_sensor(bme680.Pget_humidity_sensor(), config[CONF_HUMIDITY])
|
||||
sensor.setup_sensor(bme680.Pget_gas_resistance_sensor(), config[CONF_GAS_RESISTANCE])
|
||||
setup_component(bme680, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BME680'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_PRESSURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_HUMIDITY]),
|
||||
sensor.core_to_hass_config(data, config[CONF_GAS_RESISTANCE])]
|
||||
50
esphome/components/sensor/bmp085.py
Normal file
50
esphome/components/sensor/bmp085.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_PRESSURE, CONF_TEMPERATURE, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import HexIntLiteral, Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
BMP085Component = sensor.sensor_ns.class_('BMP085Component', PollingComponent, i2c.I2CDevice)
|
||||
BMP085TemperatureSensor = sensor.sensor_ns.class_('BMP085TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BMP085PressureSensor = sensor.sensor_ns.class_('BMP085PressureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP085Component),
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP085TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_PRESSURE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP085PressureSensor),
|
||||
})),
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
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 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_ADDRESS in config:
|
||||
add(bmp.set_address(HexIntLiteral(config[CONF_ADDRESS])))
|
||||
|
||||
sensor.setup_sensor(bmp.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(bmp.Pget_pressure_sensor(), config[CONF_PRESSURE])
|
||||
setup_component(bmp, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BMP085_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_PRESSURE])]
|
||||
82
esphome/components/sensor/bmp280.py
Normal file
82
esphome/components/sensor/bmp280.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_IIR_FILTER, CONF_NAME, \
|
||||
CONF_OVERSAMPLING, CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
BMP280Oversampling = sensor.sensor_ns.enum('BMP280Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
|
||||
'1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X,
|
||||
'2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X,
|
||||
'4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X,
|
||||
'8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X,
|
||||
'16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BMP280IIRFilter = sensor.sensor_ns.enum('BMP280IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
|
||||
'2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X,
|
||||
'4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X,
|
||||
'8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X,
|
||||
'16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BMP280_OVERSAMPLING_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||
vol.Optional(CONF_OVERSAMPLING): cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})
|
||||
|
||||
BMP280Component = sensor.sensor_ns.class_('BMP280Component', PollingComponent, i2c.I2CDevice)
|
||||
BMP280TemperatureSensor = sensor.sensor_ns.class_('BMP280TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
BMP280PressureSensor = sensor.sensor_ns.class_('BMP280PressureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP280Component),
|
||||
vol.Optional(CONF_ADDRESS, default=0x77): cv.i2c_address,
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(BMP280_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP280TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_PRESSURE): cv.nameable(BMP280_OVERSAMPLING_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BMP280PressureSensor),
|
||||
})),
|
||||
vol.Optional(CONF_IIR_FILTER): cv.one_of(*IIR_FILTER_OPTIONS, upper=True),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_bmp280_sensor(config[CONF_TEMPERATURE][CONF_NAME],
|
||||
config[CONF_PRESSURE][CONF_NAME],
|
||||
config[CONF_ADDRESS],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
bmp280 = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_OVERSAMPLING in config[CONF_TEMPERATURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_TEMPERATURE][CONF_OVERSAMPLING]]
|
||||
add(bmp280.set_temperature_oversampling(constant))
|
||||
if CONF_OVERSAMPLING in config[CONF_PRESSURE]:
|
||||
constant = OVERSAMPLING_OPTIONS[config[CONF_PRESSURE][CONF_OVERSAMPLING]]
|
||||
add(bmp280.set_pressure_oversampling(constant))
|
||||
if CONF_IIR_FILTER in config:
|
||||
constant = IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]
|
||||
add(bmp280.set_iir_filter(constant))
|
||||
|
||||
sensor.setup_sensor(bmp280.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(bmp280.Pget_pressure_sensor(), config[CONF_PRESSURE])
|
||||
setup_component(bmp280, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_BMP280'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_PRESSURE])]
|
||||
67
esphome/components/sensor/cse7766.py
Normal file
67
esphome/components/sensor/cse7766.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.components.uart import UARTComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_NAME, CONF_POWER, CONF_UART_ID, \
|
||||
CONF_UPDATE_INTERVAL, CONF_VOLTAGE
|
||||
from esphome.cpp_generator import Pvariable, get_variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
CSE7766Component = sensor.sensor_ns.class_('CSE7766Component', PollingComponent, uart.UARTDevice)
|
||||
CSE7766VoltageSensor = sensor.sensor_ns.class_('CSE7766VoltageSensor',
|
||||
sensor.EmptySensor)
|
||||
CSE7766CurrentSensor = sensor.sensor_ns.class_('CSE7766CurrentSensor',
|
||||
sensor.EmptySensor)
|
||||
CSE7766PowerSensor = sensor.sensor_ns.class_('CSE7766PowerSensor',
|
||||
sensor.EmptySensor)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CSE7766Component),
|
||||
cv.GenerateID(CONF_UART_ID): cv.use_variable_id(UARTComponent),
|
||||
|
||||
vol.Optional(CONF_VOLTAGE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CSE7766VoltageSensor),
|
||||
})),
|
||||
vol.Optional(CONF_CURRENT): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CSE7766CurrentSensor),
|
||||
})),
|
||||
vol.Optional(CONF_POWER): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CSE7766PowerSensor),
|
||||
})),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_least_one_key(CONF_VOLTAGE, CONF_CURRENT,
|
||||
CONF_POWER))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for uart_ in get_variable(config[CONF_UART_ID]):
|
||||
yield
|
||||
|
||||
rhs = App.make_cse7766(uart_, config.get(CONF_UPDATE_INTERVAL))
|
||||
cse = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sensor.register_sensor(cse.make_voltage_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sensor.register_sensor(cse.make_current_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sensor.register_sensor(cse.make_power_sensor(conf[CONF_NAME]), conf)
|
||||
setup_component(cse, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_CSE7766'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
ret = []
|
||||
for key in (CONF_VOLTAGE, CONF_CURRENT, CONF_POWER):
|
||||
if key in config:
|
||||
ret.append(sensor.core_to_hass_config(data, config[key]))
|
||||
return ret
|
||||
37
esphome/components/sensor/custom.py
Normal file
37
esphome/components/sensor/custom.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_SENSORS
|
||||
from esphome.cpp_generator import Pvariable, add, process_lambda, variable
|
||||
from esphome.cpp_types import std_vector
|
||||
|
||||
CustomSensorConstructor = sensor.sensor_ns.class_('CustomSensorConstructor')
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(CustomSensorConstructor),
|
||||
vol.Required(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(sensor.Sensor),
|
||||
})),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for template_ in process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=std_vector.template(sensor.SensorPtr)):
|
||||
yield
|
||||
|
||||
rhs = CustomSensorConstructor(template_)
|
||||
custom = variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_SENSORS]):
|
||||
var = Pvariable(conf[CONF_ID], custom.get_sensor(i))
|
||||
add(var.set_name(conf[CONF_NAME]))
|
||||
sensor.setup_sensor(var, conf)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_CUSTOM_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, sens) for sens in config[CONF_SENSORS]]
|
||||
38
esphome/components/sensor/dallas.py
Normal file
38
esphome/components/sensor/dallas.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
from esphome.components.dallas import DallasComponent
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_NAME, \
|
||||
CONF_RESOLUTION
|
||||
from esphome.cpp_generator import HexIntLiteral, get_variable
|
||||
|
||||
DallasTemperatureSensor = sensor.sensor_ns.class_('DallasTemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DallasTemperatureSensor),
|
||||
vol.Exclusive(CONF_ADDRESS, 'dallas'): cv.hex_int,
|
||||
vol.Exclusive(CONF_INDEX, 'dallas'): cv.positive_int,
|
||||
cv.GenerateID(CONF_DALLAS_ID): cv.use_variable_id(DallasComponent),
|
||||
vol.Optional(CONF_RESOLUTION): vol.All(vol.Coerce(int), vol.Range(min=9, max=12)),
|
||||
}), cv.has_at_least_one_key(CONF_ADDRESS, CONF_INDEX))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for hub in get_variable(config[CONF_DALLAS_ID]):
|
||||
yield
|
||||
if CONF_ADDRESS in config:
|
||||
address = HexIntLiteral(config[CONF_ADDRESS])
|
||||
rhs = hub.Pget_sensor_by_address(config[CONF_NAME], address, config.get(CONF_RESOLUTION))
|
||||
else:
|
||||
rhs = hub.Pget_sensor_by_index(config[CONF_NAME], config[CONF_INDEX],
|
||||
config.get(CONF_RESOLUTION))
|
||||
sensor.register_sensor(rhs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DALLAS_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
64
esphome/components/sensor/dht.py
Normal file
64
esphome/components/sensor/dht.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_NAME, \
|
||||
CONF_PIN, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
from esphome.pins import gpio_input_pullup_pin_schema
|
||||
|
||||
DHTModel = sensor.sensor_ns.enum('DHTModel')
|
||||
DHT_MODELS = {
|
||||
'AUTO_DETECT': DHTModel.DHT_MODEL_AUTO_DETECT,
|
||||
'DHT11': DHTModel.DHT_MODEL_DHT11,
|
||||
'DHT22': DHTModel.DHT_MODEL_DHT22,
|
||||
'AM2302': DHTModel.DHT_MODEL_AM2302,
|
||||
'RHT03': DHTModel.DHT_MODEL_RHT03,
|
||||
'SI7021': DHTModel.DHT_MODEL_SI7021,
|
||||
}
|
||||
|
||||
DHTComponent = sensor.sensor_ns.class_('DHTComponent', PollingComponent)
|
||||
DHTTemperatureSensor = sensor.sensor_ns.class_('DHTTemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
DHTHumiditySensor = sensor.sensor_ns.class_('DHTHumiditySensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHTComponent),
|
||||
vol.Required(CONF_PIN): gpio_input_pullup_pin_schema,
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHTTemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_HUMIDITY): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHTHumiditySensor),
|
||||
})),
|
||||
vol.Optional(CONF_MODEL): cv.one_of(*DHT_MODELS, upper=True),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_output_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_dht_sensor(config[CONF_TEMPERATURE][CONF_NAME],
|
||||
config[CONF_HUMIDITY][CONF_NAME],
|
||||
pin, config.get(CONF_UPDATE_INTERVAL))
|
||||
dht = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_MODEL in config:
|
||||
constant = DHT_MODELS[config[CONF_MODEL]]
|
||||
add(dht.set_dht_model(constant))
|
||||
|
||||
sensor.setup_sensor(dht.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(dht.Pget_humidity_sensor(), config[CONF_HUMIDITY])
|
||||
setup_component(dht, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DHT_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_HUMIDITY])]
|
||||
47
esphome/components/sensor/dht12.py
Normal file
47
esphome/components/sensor/dht12.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_NAME, CONF_TEMPERATURE, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
DHT12Component = sensor.sensor_ns.class_('DHT12Component', PollingComponent, i2c.I2CDevice)
|
||||
DHT12TemperatureSensor = sensor.sensor_ns.class_('DHT12TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
DHT12HumiditySensor = sensor.sensor_ns.class_('DHT12HumiditySensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHT12Component),
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHT12TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_HUMIDITY): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DHT12HumiditySensor),
|
||||
})),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_dht12_sensor(config[CONF_TEMPERATURE][CONF_NAME],
|
||||
config[CONF_HUMIDITY][CONF_NAME],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
dht = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
sensor.setup_sensor(dht.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(dht.Pget_humidity_sensor(), config[CONF_HUMIDITY])
|
||||
setup_component(dht, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DHT12_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_HUMIDITY])]
|
||||
34
esphome/components/sensor/duty_cycle.py
Normal file
34
esphome/components/sensor/duty_cycle.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_PIN, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DutyCycleSensor = sensor.sensor_ns.class_('DutyCycleSensor', sensor.PollingSensorComponent)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(DutyCycleSensor),
|
||||
vol.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for pin in gpio_input_pin_expression(config[CONF_PIN]):
|
||||
yield
|
||||
rhs = App.make_duty_cycle_sensor(config[CONF_NAME], pin,
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
duty = Pvariable(config[CONF_ID], rhs)
|
||||
sensor.setup_sensor(duty, config)
|
||||
setup_component(duty, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_DUTY_CYCLE_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
32
esphome/components/sensor/esp32_hall.py
Normal file
32
esphome/components/sensor/esp32_hall.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_UPDATE_INTERVAL, ESP_PLATFORM_ESP32
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Application
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
|
||||
MakeESP32HallSensor = Application.struct('MakeESP32HallSensor')
|
||||
ESP32HallSensor = sensor.sensor_ns.class_('ESP32HallSensor', sensor.PollingSensorComponent)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ESP32HallSensor),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_esp32_hall_sensor(config[CONF_NAME], config.get(CONF_UPDATE_INTERVAL))
|
||||
hall = Pvariable(config[CONF_ID], rhs)
|
||||
sensor.setup_sensor(hall, config)
|
||||
setup_component(hall, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ESP32_HALL_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
47
esphome/components/sensor/hdc1080.py
Normal file
47
esphome/components/sensor/hdc1080.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_NAME, CONF_TEMPERATURE, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
HDC1080Component = sensor.sensor_ns.class_('HDC1080Component', PollingComponent, i2c.I2CDevice)
|
||||
HDC1080TemperatureSensor = sensor.sensor_ns.class_('HDC1080TemperatureSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
HDC1080HumiditySensor = sensor.sensor_ns.class_('HDC1080HumiditySensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HDC1080Component),
|
||||
vol.Required(CONF_TEMPERATURE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HDC1080TemperatureSensor),
|
||||
})),
|
||||
vol.Required(CONF_HUMIDITY): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HDC1080HumiditySensor),
|
||||
})),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema)
|
||||
|
||||
|
||||
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 = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
sensor.setup_sensor(hdc1080.Pget_temperature_sensor(), config[CONF_TEMPERATURE])
|
||||
sensor.setup_sensor(hdc1080.Pget_humidity_sensor(), config[CONF_HUMIDITY])
|
||||
setup_component(hdc1080, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HDC1080_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return [sensor.core_to_hass_config(data, config[CONF_TEMPERATURE]),
|
||||
sensor.core_to_hass_config(data, config[CONF_HUMIDITY])]
|
||||
76
esphome/components/sensor/hlw8012.py
Normal file
76
esphome/components/sensor/hlw8012.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CF1_PIN, CONF_CF_PIN, CONF_CHANGE_MODE_EVERY, CONF_CURRENT, \
|
||||
CONF_CURRENT_RESISTOR, CONF_ID, CONF_NAME, CONF_POWER, CONF_SEL_PIN, CONF_UPDATE_INTERVAL, \
|
||||
CONF_VOLTAGE, CONF_VOLTAGE_DIVIDER
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
HLW8012Component = sensor.sensor_ns.class_('HLW8012Component', PollingComponent)
|
||||
HLW8012VoltageSensor = sensor.sensor_ns.class_('HLW8012VoltageSensor', sensor.EmptySensor)
|
||||
HLW8012CurrentSensor = sensor.sensor_ns.class_('HLW8012CurrentSensor', sensor.EmptySensor)
|
||||
HLW8012PowerSensor = sensor.sensor_ns.class_('HLW8012PowerSensor', sensor.EmptyPollingParentSensor)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HLW8012Component),
|
||||
vol.Required(CONF_SEL_PIN): pins.gpio_output_pin_schema,
|
||||
vol.Required(CONF_CF_PIN): pins.input_pin,
|
||||
vol.Required(CONF_CF1_PIN): pins.input_pin,
|
||||
|
||||
vol.Optional(CONF_VOLTAGE): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HLW8012VoltageSensor),
|
||||
})),
|
||||
vol.Optional(CONF_CURRENT): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HLW8012CurrentSensor),
|
||||
})),
|
||||
vol.Optional(CONF_POWER): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HLW8012PowerSensor),
|
||||
})),
|
||||
|
||||
vol.Optional(CONF_CURRENT_RESISTOR): cv.resistance,
|
||||
vol.Optional(CONF_VOLTAGE_DIVIDER): cv.positive_float,
|
||||
vol.Optional(CONF_CHANGE_MODE_EVERY): vol.All(cv.uint32_t, vol.Range(min=1)),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_least_one_key(CONF_VOLTAGE, CONF_CURRENT,
|
||||
CONF_POWER))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
for sel in gpio_output_pin_expression(config[CONF_SEL_PIN]):
|
||||
yield
|
||||
|
||||
rhs = App.make_hlw8012(sel, config[CONF_CF_PIN], config[CONF_CF1_PIN],
|
||||
config.get(CONF_UPDATE_INTERVAL))
|
||||
hlw = Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sensor.register_sensor(hlw.make_voltage_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sensor.register_sensor(hlw.make_current_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sensor.register_sensor(hlw.make_power_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_CURRENT_RESISTOR in config:
|
||||
add(hlw.set_current_resistor(config[CONF_CURRENT_RESISTOR]))
|
||||
if CONF_VOLTAGE_DIVIDER in config:
|
||||
add(hlw.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER]))
|
||||
if CONF_CHANGE_MODE_EVERY in config:
|
||||
add(hlw.set_change_mode_every(config[CONF_CHANGE_MODE_EVERY]))
|
||||
setup_component(hlw, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HLW8012'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
ret = []
|
||||
for key in (CONF_VOLTAGE, CONF_CURRENT, CONF_POWER):
|
||||
if key in config:
|
||||
ret.append(sensor.core_to_hass_config(data, config[key]))
|
||||
return ret
|
||||
97
esphome/components/sensor/hmc5883l.py
Normal file
97
esphome/components/sensor/hmc5883l.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# coding=utf-8
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_RANGE, CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, PollingComponent
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
CONF_FIELD_STRENGTH_X = 'field_strength_x'
|
||||
CONF_FIELD_STRENGTH_Y = 'field_strength_y'
|
||||
CONF_FIELD_STRENGTH_Z = 'field_strength_z'
|
||||
CONF_HEADING = 'heading'
|
||||
|
||||
HMC5883LComponent = sensor.sensor_ns.class_('HMC5883LComponent', PollingComponent, i2c.I2CDevice)
|
||||
HMC5883LFieldStrengthSensor = sensor.sensor_ns.class_('HMC5883LFieldStrengthSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
HMC5883LHeadingSensor = sensor.sensor_ns.class_('HMC5883LHeadingSensor',
|
||||
sensor.EmptyPollingParentSensor)
|
||||
|
||||
HMC5883LRange = sensor.sensor_ns.enum('HMC5883LRange')
|
||||
HMC5883L_RANGES = {
|
||||
88: HMC5883LRange.HMC5883L_RANGE_88_UT,
|
||||
130: HMC5883LRange.HMC5883L_RANGE_130_UT,
|
||||
190: HMC5883LRange.HMC5883L_RANGE_190_UT,
|
||||
250: HMC5883LRange.HMC5883L_RANGE_250_UT,
|
||||
400: HMC5883LRange.HMC5883L_RANGE_400_UT,
|
||||
470: HMC5883LRange.HMC5883L_RANGE_470_UT,
|
||||
560: HMC5883LRange.HMC5883L_RANGE_560_UT,
|
||||
810: HMC5883LRange.HMC5883L_RANGE_810_UT,
|
||||
}
|
||||
|
||||
|
||||
def validate_range(value):
|
||||
value = cv.string(value)
|
||||
if value.endswith(u'µT') or value.endswith('uT'):
|
||||
value = value[:-2]
|
||||
return cv.one_of(*HMC5883L_RANGES, int=True)(value)
|
||||
|
||||
|
||||
SENSOR_KEYS = [CONF_FIELD_STRENGTH_X, CONF_FIELD_STRENGTH_Y, CONF_FIELD_STRENGTH_Z,
|
||||
CONF_HEADING]
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(sensor.PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HMC5883LComponent),
|
||||
vol.Optional(CONF_ADDRESS): cv.i2c_address,
|
||||
vol.Optional(CONF_FIELD_STRENGTH_X): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HMC5883LFieldStrengthSensor),
|
||||
})),
|
||||
vol.Optional(CONF_FIELD_STRENGTH_Y): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HMC5883LFieldStrengthSensor),
|
||||
})),
|
||||
vol.Optional(CONF_FIELD_STRENGTH_Z): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HMC5883LFieldStrengthSensor),
|
||||
})),
|
||||
vol.Optional(CONF_HEADING): cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HMC5883LHeadingSensor),
|
||||
})),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
vol.Optional(CONF_RANGE): validate_range,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_least_one_key(*SENSOR_KEYS))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_hmc5883l(config.get(CONF_UPDATE_INTERVAL))
|
||||
hmc = Pvariable(config[CONF_ID], rhs)
|
||||
if CONF_ADDRESS in config:
|
||||
add(hmc.set_address(config[CONF_ADDRESS]))
|
||||
if CONF_RANGE in config:
|
||||
add(hmc.set_range(HMC5883L_RANGES[config[CONF_RANGE]]))
|
||||
if CONF_FIELD_STRENGTH_X in config:
|
||||
conf = config[CONF_FIELD_STRENGTH_X]
|
||||
sensor.register_sensor(hmc.Pmake_x_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_FIELD_STRENGTH_Y in config:
|
||||
conf = config[CONF_FIELD_STRENGTH_Y]
|
||||
sensor.register_sensor(hmc.Pmake_y_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_FIELD_STRENGTH_Z in config:
|
||||
conf = config[CONF_FIELD_STRENGTH_Z]
|
||||
sensor.register_sensor(hmc.Pmake_z_sensor(conf[CONF_NAME]), conf)
|
||||
if CONF_HEADING in config:
|
||||
conf = config[CONF_HEADING]
|
||||
sensor.register_sensor(hmc.Pmake_heading_sensor(conf[CONF_NAME]), conf)
|
||||
setup_component(hmc, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HMC5883L'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
ret = []
|
||||
for key in (CONF_FIELD_STRENGTH_X, CONF_FIELD_STRENGTH_Y, CONF_FIELD_STRENGTH_Z, CONF_HEADING):
|
||||
if key in config:
|
||||
ret.append(sensor.core_to_hass_config(data, config[key]))
|
||||
return ret
|
||||
29
esphome/components/sensor/homeassistant.py
Normal file
29
esphome/components/sensor/homeassistant.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENTITY_ID, CONF_ID, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DEPENDENCIES = ['api']
|
||||
|
||||
HomeassistantSensor = sensor.sensor_ns.class_('HomeassistantSensor', sensor.Sensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HomeassistantSensor),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_homeassistant_sensor(config[CONF_NAME], config[CONF_ENTITY_ID])
|
||||
subs = Pvariable(config[CONF_ID], rhs)
|
||||
sensor.setup_sensor(subs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HOMEASSISTANT_SENSOR'
|
||||
|
||||
|
||||
def to_hass_config(data, config):
|
||||
return sensor.core_to_hass_config(data, config)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user