Merge pull request #1012 from balloob/restart-service

Restart service
This commit is contained in:
Ryan Kraus 2016-02-02 07:15:40 -05:00
commit 1a5d18fd66
4 changed files with 92 additions and 41 deletions

View File

@ -1,7 +1,10 @@
""" Starts home assistant. """
from __future__ import print_function
from multiprocessing import Process
import signal
import sys
import threading
import os
import argparse
@ -204,6 +207,64 @@ def uninstall_osx():
print("Home Assistant has been uninstalled.")
def setup_and_run_hass(config_dir, args):
""" Setup HASS and run. Block until stopped. """
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
if args.open_ui:
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
hass.start()
sys.exit(int(hass.block_till_stopped()))
def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """
requested_stop = threading.Event()
hass_proc.daemon = True
def request_stop():
""" request hass stop """
requested_stop.set()
hass_proc.terminate()
try:
signal.signal(signal.SIGTERM, request_stop)
except ValueError:
print('Could not bind to SIGTERM. Are you running in a thread?')
hass_proc.start()
try:
hass_proc.join()
except KeyboardInterrupt:
request_stop()
try:
hass_proc.join()
except KeyboardInterrupt:
return False
return not requested_stop.isSet() and hass_proc.exitcode == 100
def main():
""" Starts Home Assistant. """
validate_python()
@ -233,33 +294,12 @@ def main():
if args.pid_file:
write_pid(args.pid_file)
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
# Run hass as child process. Restart if necessary.
keep_running = True
while keep_running:
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
keep_running = run_hass_process(hass_proc)
if args.open_ui:
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
hass.start()
hass.block_till_stopped()
if __name__ == "__main__":
main()

View File

@ -126,6 +126,7 @@ ATTR_GPS_ACCURACY = 'gps_accuracy'
# #### SERVICES ####
SERVICE_HOMEASSISTANT_STOP = "stop"
SERVICE_HOMEASSISTANT_RESTART = "restart"
SERVICE_TURN_ON = 'turn_on'
SERVICE_TURN_OFF = 'turn_off'

View File

@ -16,7 +16,8 @@ from collections import namedtuple
from homeassistant.const import (
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA)
@ -47,6 +48,9 @@ _LOGGER = logging.getLogger(__name__)
# Temporary to support deprecated methods
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
# The exit code to send to request a restart
RESTART_EXIT_CODE = 100
class HomeAssistant(object):
"""Root object of the Home Assistant home automation."""
@ -70,28 +74,33 @@ class HomeAssistant(object):
def block_till_stopped(self):
"""Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event()
request_restart = threading.Event()
def stop_homeassistant(*args):
"""Stop Home Assistant."""
request_shutdown.set()
def restart_homeassistant(*args):
"""Reset Home Assistant."""
request_restart.set()
request_shutdown.set()
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant)
if os.name != "nt":
try:
signal.signal(signal.SIGTERM, stop_homeassistant)
except ValueError:
_LOGGER.warning(
'Could not bind to SIGQUIT. Are you running in a thread?')
try:
signal.signal(signal.SIGTERM, stop_homeassistant)
except ValueError:
_LOGGER.warning(
'Could not bind to SIGTERM. Are you running in a thread?')
while not request_shutdown.isSet():
try:
time.sleep(1)
except KeyboardInterrupt:
break
time.sleep(1)
self.stop()
return RESTART_EXIT_CODE if request_restart.isSet() else 0
def stop(self):
"""Stop Home Assistant and shuts down all threads."""

View File

@ -7,6 +7,7 @@ Provides tests to verify that Home Assistant core works.
# pylint: disable=protected-access,too-many-public-methods
# pylint: disable=too-few-public-methods
import os
import signal
import unittest
from unittest.mock import patch
import time
@ -79,15 +80,15 @@ class TestHomeAssistant(unittest.TestCase):
self.assertFalse(blocking_thread.is_alive())
def test_stopping_with_keyboardinterrupt(self):
def test_stopping_with_sigterm(self):
calls = []
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: calls.append(1))
def raise_keyboardinterrupt(length):
raise KeyboardInterrupt
def send_sigterm(length):
os.kill(os.getpid(), signal.SIGTERM)
with patch('homeassistant.core.time.sleep', raise_keyboardinterrupt):
with patch('homeassistant.core.time.sleep', send_sigterm):
self.hass.block_till_stopped()
self.assertEqual(1, len(calls))