diff --git a/README.md b/README.md index c501649edc2..e28a155f866 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Home Assistant ============== -Home Assistant provides a platform for home automation. It does so by having modules that observe and trigger actors to do various tasks. +Home Assistant is a home automation platform running on Python 3. It provides modules that observe and trigger actors to do various tasks. It is currently able to do the following things: * Track if devices are home by monitoring connected devices to a wireless router (currently supporting modern Netgear routers or routers running Tomato firmware) diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index f8c488f97e2..9d67ec8eeba 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -171,7 +171,7 @@ def create_bus_job_handler(logger): except Exception: # pylint: disable=broad-except # Catch any exception our service/event_listener might throw # We do not want to crash our ThreadPool - logger.exception(u"BusHandler:Exception doing job") + logger.exception("BusHandler:Exception doing job") return job_handler @@ -189,10 +189,10 @@ class ServiceCall(object): def __repr__(self): if self.data: - return u"".format( + return "".format( self.domain, self.service, util.repr_helper(self.data)) else: - return u"".format(self.domain, self.service) + return "".format(self.domain, self.service) # pylint: disable=too-few-public-methods @@ -207,10 +207,10 @@ class Event(object): def __repr__(self): if self.data: - return u"".format( + return "".format( self.event_type, util.repr_helper(self.data)) else: - return u"".format(self.event_type) + return "".format(self.event_type) class Bus(object): @@ -235,7 +235,7 @@ class Bus(object): def services(self): """ Dict with per domain a list of available services. """ with self.service_lock: - return {domain: self._services[domain].keys() + return {domain: list(self._services[domain].keys()) for domain in self._services} @property @@ -268,7 +268,7 @@ class Bus(object): except KeyError: # if key domain or service does not exist raise ServiceDoesNotExistError( - u"Service does not exist: {}/{}".format(domain, service)) + "Service does not exist: {}/{}".format(domain, service)) def register_service(self, domain, service, service_func): """ Register a service. """ @@ -290,7 +290,7 @@ class Bus(object): event = Event(event_type, event_data) - self.logger.info(u"Bus:Handling {}".format(event)) + self.logger.info("Bus:Handling {}".format(event)) if not listeners: return @@ -363,7 +363,7 @@ class Bus(object): def _check_busy(self): """ Complain if we have more than twice as many jobs queued as threads and if we didn't complain about it recently. """ - if self.pool.queue.qsize() / self.thread_count >= 2 and \ + if self.pool.work_queue.qsize() / self.thread_count >= 2 and \ dt.datetime.now()-self.last_busy_notice > BUS_REPORT_BUSY_TIMEOUT: self.last_busy_notice = dt.datetime.now() @@ -371,13 +371,13 @@ class Bus(object): log_error = self.logger.error log_error( - u"Bus:All {} threads are busy and {} jobs pending".format( - self.thread_count, self.pool.queue.qsize())) + "Bus:All {} threads are busy and {} jobs pending".format( + self.thread_count, self.pool.work_queue.qsize())) jobs = self.pool.current_jobs for start, job in jobs: - log_error(u"Bus:Current job from {}: {}".format( + log_error("Bus:Current job from {}: {}".format( util.datetime_to_str(start), job)) @@ -436,11 +436,11 @@ class State(object): def __repr__(self): if self.attributes: - return u"".format( + return "".format( self.state, util.repr_helper(self.attributes), util.datetime_to_str(self.last_changed)) else: - return u"".format( + return "".format( self.state, util.datetime_to_str(self.last_changed)) @@ -456,7 +456,7 @@ class StateMachine(object): def entity_ids(self): """ List of entitie ids that are being tracked. """ with self.lock: - return self.states.keys() + return list(self.states.keys()) def remove_entity(self, entity_id): """ Removes a entity from the state machine. @@ -515,9 +515,9 @@ class StateMachine(object): def is_state(self, entity_id, state): """ Returns True if entity exists and is specified state. """ try: - return self.get_state(entity_id).state == state + return self.states.get(entity_id).state == state except AttributeError: - # get_state returned None + # states.get returned None return False diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 7aee8aa0add..5bd20bb1bcb 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -3,7 +3,7 @@ Provides methods to bootstrap a home assistant instance. """ import importlib -import ConfigParser +import configparser import logging import homeassistant as ha @@ -33,7 +33,7 @@ def from_config_file(config_path): statusses = [] # Read config - config = ConfigParser.SafeConfigParser() + config = configparser.SafeConfigParser() config.read(config_path) # Init core @@ -51,7 +51,7 @@ def from_config_file(config_path): """ Failure proof option retriever. """ try: return config.get(section, option) - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + except (configparser.NoSectionError, configparser.NoOptionError): return default # Device scanner @@ -83,7 +83,7 @@ def from_config_file(config_path): get_opt('device_tracker.netgear', 'username'), get_opt('device_tracker.netgear', 'password')) - except ConfigParser.NoOptionError: + except configparser.NoOptionError: # If one of the options didn't exist logger.exception(("Error initializing {}DeviceScanner, " "could not find one of the following config " @@ -142,7 +142,11 @@ def from_config_file(config_path): add_status("Light - Hue", light_control.success_init) - light.setup(bus, statemachine, light_control) + if light_control.success_init: + light.setup(bus, statemachine, light_control) + else: + light_control = None + else: light_control = None diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index abad1919169..b6ae5be8a7c 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -113,7 +113,7 @@ def setup(bus, statemachine): entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format( util.slugify(cast.device.friendly_name)), - casts.keys()) + list(casts.keys())) casts[entity_id] = cast @@ -189,8 +189,7 @@ def setup(bus, statemachine): yield entity_id, cast else: - for item in casts.items(): - yield item + yield from casts.items() def turn_off_service(service): """ Service to exit any running app on the specified ChromeCast and @@ -230,7 +229,7 @@ def setup(bus, statemachine): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: - ramp.next() + next(ramp) update_chromecast_state(entity_id, cast) def play_youtube_video_service(service, video_id): diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 52b42b5416e..060c073bad3 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -51,7 +51,7 @@ def setup(bus, statemachine, next_setting = sun.next_setting(statemachine) if next_setting: - return (next_setting - LIGHT_TRANSITION_TIME * len(light_ids)) + return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) else: return None diff --git a/homeassistant/components/device_tracker.py b/homeassistant/components/device_tracker.py index 757ebe103d0..945106255c0 100644 --- a/homeassistant/components/device_tracker.py +++ b/homeassistant/components/device_tracker.py @@ -124,7 +124,7 @@ class DeviceTracker(object): # Because we do not want to have stuff happening when the device does # not show up for 1 scan beacuse of reboot etc for device in temp_tracking_devices: - if (now - known_dev[device]['last_seen'] > self.error_scanning): + if now - known_dev[device]['last_seen'] > self.error_scanning: self.statemachine.set_state(known_dev[device]['entity_id'], components.STATE_NOT_HOME) diff --git a/homeassistant/components/httpinterface/__init__.py b/homeassistant/components/httpinterface/__init__.py index fe66c72d5bf..2da48945955 100644 --- a/homeassistant/components/httpinterface/__init__.py +++ b/homeassistant/components/httpinterface/__init__.py @@ -72,8 +72,8 @@ import threading import logging import re import os -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from urlparse import urlparse, parse_qs +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlparse, parse_qs import homeassistant as ha import homeassistant.util as util @@ -138,6 +138,7 @@ class HTTPInterface(threading.Thread): self.server.serve_forever() +# pylint: disable=too-many-public-methods class RequestHandler(BaseHTTPRequestHandler): """ Handles incoming HTTP requests """ @@ -188,7 +189,8 @@ class RequestHandler(BaseHTTPRequestHandler): content_length = int(self.headers.get('Content-Length', 0)) if content_length: - data.update(parse_qs(self.rfile.read(content_length))) + data.update(parse_qs(self.rfile.read( + content_length).decode("UTF-8"))) try: api_password = data['api_password'][0] @@ -282,7 +284,7 @@ class RequestHandler(BaseHTTPRequestHandler): "" "" - "").format(self.path)) + "").format(self.path).encode("UTF-8")) return False @@ -290,7 +292,7 @@ class RequestHandler(BaseHTTPRequestHandler): def _handle_get_root(self, path_match, data): """ Renders the debug interface. """ - write = lambda txt: self.wfile.write(txt.encode("UTF-8")+"\n") + write = lambda txt: self.wfile.write((txt + "\n").encode("UTF-8")) self.send_response(HTTP_OK) self.send_header('Content-type', 'text/html; charset=utf-8') @@ -335,13 +337,13 @@ class RequestHandler(BaseHTTPRequestHandler): state = self.server.statemachine.get_state(entity_id) - attributes = u"
".join( - [u"{}: {}".format(attr, state.attributes[attr]) + attributes = "
".join( + ["{}: {}".format(attr, state.attributes[attr]) for attr in state.attributes]) - write((u"" - u"{}{}{}{}" - u"").format( + write(("" + "{}{}{}{}" + "").format( entity_id, state.state, attributes, @@ -686,4 +688,5 @@ class RequestHandler(BaseHTTPRequestHandler): self.end_headers() if data: - self.wfile.write(json.dumps(data, indent=4, sort_keys=True)) + self.wfile.write( + json.dumps(data, indent=4, sort_keys=True).encode("UTF-8")) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index d8dd2c4bdb7..58a2b9e7277 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -155,11 +155,11 @@ def setup(bus, statemachine, light_control): # We have not seen this light before, set it up # Create entity id - logger.info(u"Found new light {}".format(name)) + logger.info("Found new light {}".format(name)) entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), - ent_to_light.keys()) + list(ent_to_light.keys())) ent_to_light[entity_id] = light_id light_to_ent[light_id] = entity_id @@ -218,7 +218,7 @@ def setup(bus, statemachine, light_control): file_path = os.path.join(dir_path, LIGHT_PROFILES_FILE) if os.path.isfile(file_path): - with open(file_path, 'rb') as inp: + with open(file_path) as inp: reader = csv.reader(inp) # Skip the header @@ -249,7 +249,7 @@ def setup(bus, statemachine, light_control): if entity_id in ent_to_light] if not light_ids: - light_ids = ent_to_light.values() + light_ids = list(ent_to_light.values()) transition = util.convert(dat.get(ATTR_TRANSITION), int) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index a6faba2c4dd..4270d9c5706 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -77,7 +77,7 @@ def setup(bus, statemachine): # New device, set it up entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(device.name)), - ent_to_dev.keys()) + list(ent_to_dev.keys())) sno_to_ent[device.serialnumber] = entity_id ent_to_dev[entity_id] = device @@ -115,7 +115,7 @@ def setup(bus, statemachine): # Track all lights in a group group.setup(bus, statemachine, - GROUP_NAME_ALL_WEMOS, sno_to_ent.values()) + GROUP_NAME_ALL_WEMOS, list(sno_to_ent.values())) def _handle_wemo_service(service): """ Handles calls to the WeMo service. """ diff --git a/homeassistant/external/pynetgear b/homeassistant/external/pynetgear index 9e094c32289..bc635995789 160000 --- a/homeassistant/external/pynetgear +++ b/homeassistant/external/pynetgear @@ -1 +1 @@ -Subproject commit 9e094c322890bbcdd6cf4b0af4fd763f227cd64f +Subproject commit bc635995789fc91c9b4a2fecaeb50241ab4fbf2a diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 1708a228a6a..46a383be816 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -12,7 +12,7 @@ HomeAssistantError will be raised. import threading import logging import json -import urlparse +import urllib.parse import requests @@ -34,7 +34,7 @@ def _setup_call_api(host, port, api_password): data = data or {} data['api_password'] = api_password - url = urlparse.urljoin(base_url, path) + url = urllib.parse.urljoin(base_url, path) try: if method == METHOD_GET: @@ -61,14 +61,12 @@ class JSONEncoder(json.JSONEncoder): return json.JSONEncoder.default(self, obj) -class Bus(ha.Bus): +class Bus(object): """ Drop-in replacement for a normal bus that will forward interaction to a remote bus. """ def __init__(self, host, api_password, port=None): - ha.Bus.__init__(self) - self.logger = logging.getLogger(__name__) self._call_api = _setup_call_api(host, port, api_password) @@ -172,6 +170,12 @@ class Bus(ha.Bus): self.logger.error("Bus:{}".format(error)) raise ha.HomeAssistantError(error) + def has_service(self, domain, service): + """ Not implemented for remote bus. + + Will throw NotImplementedError. """ + raise NotImplementedError + def listen_event(self, event_type, listener): """ Not implemented for remote bus. @@ -192,14 +196,12 @@ class Bus(ha.Bus): raise NotImplementedError -class StateMachine(ha.StateMachine): +class StateMachine(object): """ Drop-in replacement for a normal statemachine that communicates with a remote statemachine. """ def __init__(self, host, api_password, port=None): - ha.StateMachine.__init__(self, None) - self._call_api = _setup_call_api(host, port, api_password) self.lock = threading.Lock() @@ -297,3 +299,11 @@ class StateMachine(ha.StateMachine): self.logger.exception("StateMachine:Got unexpected result (2)") raise ha.HomeAssistantError( "Got unexpected result (2): {}".format(req.text)) + + def is_state(self, entity_id, state): + """ Returns True if entity exists and is specified state. """ + try: + return self.get_state(entity_id).state == state + except AttributeError: + # get_state returned None + return False diff --git a/homeassistant/util.py b/homeassistant/util.py index 22bbfc58855..cbb2c2f93ed 100644 --- a/homeassistant/util.py +++ b/homeassistant/util.py @@ -1,13 +1,18 @@ -""" Helper methods for various modules. """ +""" +homeassistant.util +~~~~~~~~~~~~~~~~~~ + +Helper methods for various modules. +""" import threading -import Queue +import queue import datetime import re RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+') -DATE_STR_FORMAT = u"%H:%M:%S %d-%m-%Y" +DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y" def sanitize_filename(filename): @@ -59,13 +64,13 @@ def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False): def repr_helper(inp): """ Helps creating a more readable string representation of objects. """ if isinstance(inp, dict): - return u", ".join( - repr_helper(key)+u"="+repr_helper(item) for key, item + return ", ".join( + repr_helper(key)+"="+repr_helper(item) for key, item in inp.items()) elif isinstance(inp, datetime.datetime): return datetime_to_str(inp) else: - return unicode(inp) + return str(inp) # Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py @@ -146,25 +151,38 @@ class ThreadPool(object): # pylint: disable=too-few-public-methods def __init__(self, worker_count, job_handler): - queue = self.queue = Queue.PriorityQueue() + work_queue = self.work_queue = queue.PriorityQueue() current_jobs = self.current_jobs = [] - for _ in xrange(worker_count): + for _ in range(worker_count): worker = threading.Thread(target=_threadpool_worker, - args=(queue, current_jobs, job_handler)) + args=(work_queue, current_jobs, + job_handler)) worker.daemon = True worker.start() def add_job(self, priority, job): """ Add a job to be sent to the workers. """ - self.queue.put((priority, job)) + self.work_queue.put(PriorityQueueItem(priority, job)) -def _threadpool_worker(queue, current_jobs, job_handler): +class PriorityQueueItem(object): + """ Holds a priority and a value. Used within PriorityQueue. """ + + # pylint: disable=too-few-public-methods + def __init__(self, priority, item): + self.priority = priority + self.item = item + + def __lt__(self, other): + return self.priority < other.priority + + +def _threadpool_worker(work_queue, current_jobs, job_handler): """ Provides the base functionality of a worker for the thread pool. """ while True: - # Get new item from queue - job = queue.get()[1] + # Get new item from work_queue + job = work_queue.get().item # Add to current running jobs job_log = (datetime.datetime.now(), job) @@ -176,5 +194,5 @@ def _threadpool_worker(queue, current_jobs, job_handler): # Remove from current running job current_jobs.remove(job_log) - # Tell queue a task is done - queue.task_done() + # Tell work_queue a task is done + work_queue.task_done() diff --git a/run_tests b/run_tests index 74c7159154c..fcf5f06cf0d 100755 --- a/run_tests +++ b/run_tests @@ -1,2 +1 @@ -python -B -m unittest homeassistant.test - \ No newline at end of file +python3 -B -m unittest homeassistant.test diff --git a/start.py b/start.py index 80815eec65e..16a26c780a5 100644 --- a/start.py +++ b/start.py @@ -1,4 +1,3 @@ -#!/usr/bin/python2 """ Starts home assistant with all possible functionality. """ import homeassistant.bootstrap