HassIO add-on (#18)

* HassIO Beginnings

* Updates

* Fix pylint errors

* Fix pylint error
This commit is contained in:
Otto Winter
2018-05-21 16:40:22 +02:00
committed by GitHub
parent ebb6d0d464
commit 94d7ac4ef0
12 changed files with 1140 additions and 106 deletions
+4
View File
@@ -0,0 +1,4 @@
include README.md
include esphomeyaml/hassio/templates/index.html
include esphomeyaml/hassio/static/materialize-stepper.min.css
include esphomeyaml/hassio/static/materialize-stepper.min.js
+192 -88
View File
File diff suppressed because it is too large Load Diff
@@ -4,11 +4,10 @@ import esphomeyaml.config_validation as cv
from esphomeyaml.components import switch
from esphomeyaml.components.ir_transmitter import IRTransmitterComponent
from esphomeyaml.const import CONF_ADDRESS, CONF_CARRIER_FREQUENCY, CONF_COMMAND, CONF_DATA, \
CONF_ID, CONF_INVERTED, CONF_IR_TRANSMITTER_ID, CONF_LG, CONF_NAME, CONF_NBITS, CONF_NEC, \
CONF_INVERTED, CONF_IR_TRANSMITTER_ID, CONF_LG, CONF_NAME, CONF_NBITS, CONF_NEC, \
CONF_PANASONIC, CONF_RAW, CONF_REPEAT, CONF_SONY, CONF_TIMES, CONF_WAIT_TIME
from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import App, ArrayInitializer, HexIntLiteral, Pvariable, \
get_variable
from esphomeyaml.helpers import App, ArrayInitializer, HexIntLiteral, get_variable
DEPENDENCIES = ['ir_transmitter']
@@ -98,8 +97,7 @@ def to_code(config):
ir = get_variable(config.get(CONF_IR_TRANSMITTER_ID), IRTransmitterComponent)
send_data = exp_send_data(config)
rhs = App.register_component(ir.create_transmitter(config[CONF_NAME], send_data))
switch_ = Pvariable(DataTransmitter, config[CONF_ID], rhs)
switch.register_switch(switch_, config)
switch.register_switch(rhs, config)
BUILD_FLAGS = '-DUSE_IR_TRANSMITTER'
+1 -1
View File
@@ -26,7 +26,7 @@ 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))
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword
+2 -2
View File
@@ -1,8 +1,8 @@
"""Constants used by esphomeyaml."""
MAJOR_VERSION = 1
MINOR_VERSION = 5
PATCH_VERSION = '3'
MINOR_VERSION = 6
PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
View File
+176
View File
@@ -0,0 +1,176 @@
from __future__ import print_function
import codecs
import json
import logging
import os
import random
import subprocess
try:
import tornado
import tornado.gen
import tornado.ioloop
import tornado.iostream
import tornado.process
import tornado.web
import tornado.websocket
import tornado.concurrent
except ImportError as err:
pass
from esphomeyaml import const, core, __main__
from esphomeyaml.__main__ import get_serial_ports, get_base_path, get_name
from esphomeyaml.helpers import quote
_LOGGER = logging.getLogger(__name__)
CONFIG_DIR = ''
# pylint: disable=abstract-method, arguments-differ
class EsphomeyamlCommandWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs):
super(EsphomeyamlCommandWebSocket, self).__init__(application, request, **kwargs)
self.proc = None
self.closed = False
def on_message(self, message):
if self.proc is not None:
return
command = self.build_command(message)
_LOGGER.debug(u"WebSocket opened for command %s", [quote(x) for x in command])
self.proc = tornado.process.Subprocess(command,
stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT)
self.proc.set_exit_callback(self.proc_on_exit)
tornado.ioloop.IOLoop.current().spawn_callback(self.redirect_stream)
@tornado.gen.coroutine
def redirect_stream(self):
while True:
try:
data = yield self.proc.stdout.read_until_regex('[\n\r]')
except tornado.iostream.StreamClosedError:
break
if data.endswith('\r') and random.randrange(100) < 90:
continue
data = data.replace('\033', '\\033')
self.write_message({'event': 'line', 'data': data})
def proc_on_exit(self, returncode):
if not self.closed:
_LOGGER.debug("Process exited with return code %s", returncode)
self.write_message({'event': 'exit', 'code': returncode})
def on_close(self):
self.closed = True
if self.proc is not None and self.proc.returncode is None:
_LOGGER.debug("Terminating process")
self.proc.proc.terminate()
def build_command(self, message):
raise NotImplementedError
class EsphomeyamlLogsHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = CONFIG_DIR + '/' + js['configuration']
return ["esphomeyaml", config_file, "logs", '--serial-port', js["port"], '--escape']
class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "run", '--upload-port', js["port"],
'--escape', '--use-esptoolpy']
class EsphomeyamlCompileHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "compile"]
class SerialPortRequestHandler(tornado.web.RequestHandler):
def get(self):
ports = get_serial_ports()
data = []
for port, desc in ports:
if port == '/dev/ttyAMA0':
# ignore RPi built-in serial port
continue
data.append({'port': port, 'desc': desc})
data.append({'port': 'OTA', 'desc': 'Over-The-Air Upload/Logs'})
self.write(json.dumps(data))
class WizardRequestHandler(tornado.web.RequestHandler):
def post(self):
from esphomeyaml import wizard
kwargs = {k: ''.join(v) for k, v in self.request.arguments.iteritems()}
config = wizard.wizard_file(**kwargs)
destination = os.path.join(CONFIG_DIR, kwargs['name'] + '.yaml')
with codecs.open(destination, 'w') as f_handle:
f_handle.write(config)
self.redirect('/')
class DownloadBinaryRequestHandler(tornado.web.RequestHandler):
def get(self):
configuration = self.get_argument('configuration')
config_file = os.path.join(CONFIG_DIR, configuration)
core.CONFIG_PATH = config_file
config = __main__.read_config(core.CONFIG_PATH)
name = get_name(config)
path = os.path.join(get_base_path(config), '.pioenvs', name, 'firmware.bin')
self.set_header('Content-Type', 'application/octet-stream')
self.set_header("Content-Disposition", 'attachment; filename="{}.bin"'.format(name))
with open(path, 'rb') as f:
while 1:
data = f.read(16384) # or some other nice-sized chunk
if not data:
break
self.write(data)
self.finish()
class MainRequestHandler(tornado.web.RequestHandler):
def get(self):
files = [f for f in os.listdir(CONFIG_DIR) if f.endswith('.yaml')]
full_path_files = [os.path.join(CONFIG_DIR, f) for f in files]
self.render("templates/index.html", files=files, full_path_files=full_path_files,
version=const.__version__)
def make_app():
static_path = os.path.join(os.path.dirname(__file__), 'static')
return tornado.web.Application([
(r"/", MainRequestHandler),
(r"/logs", EsphomeyamlLogsHandler),
(r"/run", EsphomeyamlRunHandler),
(r"/compile", EsphomeyamlCompileHandler),
(r"/download.bin", DownloadBinaryRequestHandler),
(r"/serial-ports", SerialPortRequestHandler),
(r"/wizard.html", WizardRequestHandler),
(r'/static/(.*)', tornado.web.StaticFileHandler, {'path': static_path}),
], debug=True)
def start_web_server(args):
global CONFIG_DIR
CONFIG_DIR = args.configuration
if not os.path.exists(CONFIG_DIR):
os.makedirs(CONFIG_DIR)
_LOGGER.info("Starting HassIO add-on web server on port %s and configuration dir %s...",
args.port, CONFIG_DIR)
app = make_app()
app.listen(args.port)
try:
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt:
_LOGGER.info("Shutting down...")
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -39,7 +39,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
return 0
def show_logs(config, topic=None, username=None, password=None, client_id=None):
def show_logs(config, topic=None, username=None, password=None, client_id=None, escape=False):
if topic is not None:
pass # already have topic
elif CONF_MQTT in config:
@@ -57,7 +57,10 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None):
def on_message(client, userdata, msg):
time = datetime.now().time().strftime(u'[%H:%M:%S]')
print(time + msg.payload)
message = msg.payload.decode('utf-8')
if escape:
message = message.replace('\033', '\\033')
print(time + message)
return initialize(config, [topic], on_message, username, password, client_id)
+16 -8
View File
@@ -70,6 +70,18 @@ logger:
"""
def wizard_file(**kwargs):
config = BASE_CONFIG.format(**kwargs)
if kwargs['ota_password']:
config += "ota:\n password: '{}'\n".format(kwargs['ota_password'])
else:
config += "ota:\n"
return config
if os.getenv('ESPHOMEYAML_QUICKWIZARD', False):
def sleep(time):
pass
@@ -272,14 +284,10 @@ def wizard(path):
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: '{}'\n".format(ota_password)
else:
config += "ota:\n"
config = wizard_file(name=name, platform=platform, board=board,
ssid=ssid, psk=psk, broker=broker,
mqtt_username=mqtt_username, mqtt_password=mqtt_password,
ota_password=ota_password)
with codecs.open(path, 'w') as f_handle:
f_handle.write(config)