Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions homeassistant/components/google_pubsub/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Support for Google Cloud Pub/Sub.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/google_pubsub/
"""
import datetime
import json
import logging
import os
from typing import Any, Dict

import voluptuous as vol

from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN)
from homeassistant.core import Event, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
Comment thread
timvancann marked this conversation as resolved.

_LOGGER = logging.getLogger(__name__)

REQUIREMENTS = ['google-cloud-pubsub==0.39.1']

DOMAIN = 'google_pubsub'

CONF_PROJECT_ID = 'project_id'
CONF_TOPIC_NAME = 'topic_name'
CONF_SERVICE_PRINCIPAL = 'credentials_json'
CONF_FILTER = 'filter'

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PROJECT_ID): cv.string,
vol.Required(CONF_TOPIC_NAME): cv.string,
vol.Required(CONF_SERVICE_PRINCIPAL): cv.string,
vol.Required(CONF_FILTER): FILTER_SCHEMA
}),
}, extra=vol.ALLOW_EXTRA)


def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]):
Comment thread
timvancann marked this conversation as resolved.
"""Activate Google Pub/Sub component."""
from google.cloud import pubsub_v1

config = yaml_config[DOMAIN]
project_id = config[CONF_PROJECT_ID]
topic_name = config[CONF_TOPIC_NAME]
service_principal_path = os.path.join(hass.config.config_dir,
config[CONF_SERVICE_PRINCIPAL])

if not os.path.isfile(service_principal_path):
_LOGGER.error("Path to credentials file cannot be found")
return False

entities_filter = config[CONF_FILTER]

publisher = (pubsub_v1
.PublisherClient
.from_service_account_json(service_principal_path)
)

topic_path = publisher.topic_path(project_id, # pylint: disable=E1101
Comment thread
MartinHjelmare marked this conversation as resolved.
topic_name)

encoder = DateTimeJSONEncoder()

def send_to_pubsub(event: Event):
Comment thread
timvancann marked this conversation as resolved.
"""Send states to Pub/Sub."""
state = event.data.get('new_state')
if (state is None
or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE)
or not entities_filter(state.entity_id)):
return

as_dict = state.as_dict()
data = json.dumps(
obj=as_dict,
default=encoder.encode
).encode('utf-8')

publisher.publish(topic_path, data=data)
Comment thread
timvancann marked this conversation as resolved.

hass.bus.listen(EVENT_STATE_CHANGED, send_to_pubsub)

return True


class DateTimeJSONEncoder(json.JSONEncoder):
Comment thread
timvancann marked this conversation as resolved.
"""Encode python objects.

Additionally add encoding for datetime objects as isoformat.
"""

def default(self, o): # pylint: disable=E0202
"""Implement encoding logic."""
if isinstance(o, datetime.datetime):
return o.isoformat()
return super().default(o)
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,9 @@ gntp==1.0.3
# homeassistant.components.google
google-api-python-client==1.6.4

# homeassistant.components.google_pubsub
google-cloud-pubsub==0.39.1

# homeassistant.components.googlehome
googledevices==1.0.2

Expand Down
22 changes: 22 additions & 0 deletions tests/components/google_pubsub/test_pubsub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""The tests for the Google Pub/Sub component."""
from datetime import datetime

from homeassistant.components.google_pubsub import (
DateTimeJSONEncoder as victim)


class TestDateTimeJSONEncoder(object):
"""Bundle for DateTimeJSONEncoder tests."""

def test_datetime(self):
"""Test datetime encoding."""
time = datetime(2019, 1, 13, 12, 30, 5)
assert victim().encode(time) == '"2019-01-13T12:30:05"'

def test_no_datetime(self):
"""Test integer encoding."""
assert victim().encode(42) == '42'

def test_nested(self):
"""Test dictionary encoding."""
assert victim().encode({'foo': 'bar'}) == '{"foo": "bar"}'