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