forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for recording history to Apache Kafka (home-assistant#25085)
* Add support for Apache Kafka * Simplified * Revert "Simplified" This reverts commit fde4624. * Revert "Revert "Simplified"" This reverts commit 5ae57e6. * Completed * Updated requirements * Updated .coveragerc * Removed unused import * Updated codeowner
- Loading branch information
Showing
5 changed files
with
128 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
"""Support for Apache Kafka.""" | ||
from datetime import datetime | ||
import json | ||
import logging | ||
|
||
from aiokafka import AIOKafkaProducer | ||
import voluptuous as vol | ||
|
||
from homeassistant.const import ( | ||
CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, | ||
STATE_UNAVAILABLE, STATE_UNKNOWN) | ||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.helpers.entityfilter import FILTER_SCHEMA | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DOMAIN = 'apache_kafka' | ||
|
||
CONF_FILTER = 'filter' | ||
CONF_TOPIC = 'topic' | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: vol.Schema({ | ||
vol.Required(CONF_IP_ADDRESS): cv.string, | ||
vol.Required(CONF_PORT): cv.port, | ||
vol.Required(CONF_TOPIC): cv.string, | ||
vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, | ||
}), | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
|
||
async def async_setup(hass, config): | ||
"""Activate the Apache Kafka integration.""" | ||
conf = config[DOMAIN] | ||
|
||
kafka = hass.data[DOMAIN] = KafkaManager( | ||
hass, | ||
conf[CONF_IP_ADDRESS], | ||
conf[CONF_PORT], | ||
conf[CONF_TOPIC], | ||
conf[CONF_FILTER]) | ||
|
||
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, kafka.shutdown()) | ||
|
||
await kafka.start() | ||
|
||
return True | ||
|
||
|
||
class DateTimeJSONEncoder(json.JSONEncoder): | ||
"""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): | ||
return o.isoformat() | ||
return super().default(o) | ||
|
||
|
||
class KafkaManager: | ||
"""Define a manager to buffer events to Kafka.""" | ||
|
||
def __init__( | ||
self, | ||
hass, | ||
ip_address, | ||
port, | ||
topic, | ||
entities_filter): | ||
"""Initialize.""" | ||
self._encoder = DateTimeJSONEncoder() | ||
self._entities_filter = entities_filter | ||
self._hass = hass | ||
self._producer = AIOKafkaProducer( | ||
loop=hass.loop, | ||
bootstrap_servers="{0}:{1}".format(ip_address, port), | ||
compression_type="gzip", | ||
) | ||
self._topic = topic | ||
|
||
def _encode_event(self, event): | ||
"""Translate events into a binary JSON payload.""" | ||
state = event.data.get('new_state') | ||
if (state is None | ||
or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE) | ||
or not self._entities_filter(state.entity_id)): | ||
return | ||
|
||
return json.dumps( | ||
obj=state.as_dict(), | ||
default=self._encoder.encode | ||
).encode('utf-8') | ||
|
||
async def start(self): | ||
"""Start the Kafka manager.""" | ||
self._hass.bus.async_listen(EVENT_STATE_CHANGED, self.write) | ||
await self._producer.start() | ||
|
||
async def shutdown(self): | ||
"""Shut the manager down.""" | ||
await self._producer.stop() | ||
|
||
async def write(self, event): | ||
"""Write a binary payload to Kafka.""" | ||
payload = self._encode_event(event) | ||
|
||
if payload: | ||
await self._producer.send_and_wait(self._topic, payload) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"domain": "apache_kafka", | ||
"name": "Apache Kafka", | ||
"documentation": "https://www.home-assistant.io/components/apache_kafka", | ||
"requirements": [ | ||
"aiokafka==0.5.1" | ||
], | ||
"dependencies": [], | ||
"codeowners": [ | ||
"@bachya" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters