Skip to content

Commit

Permalink
feat: create all devices in single integration
Browse files Browse the repository at this point in the history
Now we may use one integration for all devices.
We subscribes at topics in root topic and every subtopic will create own device on the fly.
Fix #2
  • Loading branch information
IATkachenko committed Feb 7, 2021
1 parent a69c0d0 commit 1cd906d
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 37 deletions.
37 changes: 36 additions & 1 deletion custom_components/sleep_as_android/__init__.py
Expand Up @@ -26,4 +26,39 @@ class SleepAsAndroidInstance:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
self.hass = hass
self._config_entry = config_entry
hass.loop.create_task(self.hass.config_entries.async_forward_entry_setup(self._config_entry, 'sensor'))
self._topic = None
try:
self._name: str = self.get_from_config('name')
except KeyError:
self._name = 'SleepAsAndroid'

try:
self._topic = self.get_from_config('root_topic')
except KeyError:
try:
self.get_from_config('topic')
_LOGGER.warning("You are using deprecated configuration with one topic per integration. \n"
"Please remove additional integration and set root topic for all devices instead.")
self._topic = '/'.join(self._config_entry.data['topic'].split('/')[:-1])
except KeyError:
_LOGGER.critical("Could not find topic or root_topic in configuration. Will use %s instead",
self._topic)
self._topic = 'SleepAsAndroid'

self.hass.loop.create_task(self.hass.config_entries.async_forward_entry_setup(self._config_entry, 'sensor'))

def get_from_config(self, name: str) -> str:
try:
data = self._config_entry.options[name]
except KeyError:
data = self._config_entry.data[name]

return data

@property
def name(self) -> str:
return self._name

@property
def root_topic(self) -> str:
return self._topic
2 changes: 1 addition & 1 deletion custom_components/sleep_as_android/const.py
Expand Up @@ -4,6 +4,6 @@

schema = {
vol.Required("name", default="SleepAsAndroid"): str,
vol.Required("topic", ): str,
vol.Required("root_topic", default="SleepAsAndroid"): str,
vol.Optional("qos", default=0): int
}
122 changes: 87 additions & 35 deletions custom_components/sleep_as_android/sensor.py
Expand Up @@ -11,81 +11,133 @@
from homeassistant.components.mqtt import subscription
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import entity_registry as er

from .const import DOMAIN
from .device_trigger import TRIGGERS

from .const import DOMAIN
from . import SleepAsAndroidInstance

_LOGGER = logging.getLogger(__name__)


def get_name_from_topic(topic: str) -> str:
return topic.split('/')[-1]


def generate_id(instance: SleepAsAndroidInstance, name: str) -> str:
return instance.name + '_' + name


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities):
"""Set up the sensor entry"""
async_add_entities([SleepAsAndroidSensor(hass, config_entry)])
async def subscribe_root_topic(root_instance: SleepAsAndroidInstance):
"""(Re)Subscribe to topics."""
root_topic = root_instance.root_topic
_LOGGER.debug("Subscribing to root topic '%s'", root_topic)
sub_state = None

entity_registry = await er.async_get_registry(hass)

entities = []
for entity in er.async_entries_for_config_entry(entity_registry, config_entry.entry_id):
entities.append(SleepAsAndroidSensor(hass, config_entry, "/".join(entity.unique_id.split('_')[1:])))

async_add_entities(entities)

@callback
# @log_messages(self.hass, self.entry_id)
def message_received(msg):
"""Handle new MQTT messages."""
_LOGGER.debug("Got message %s", msg)
entity_id = root_instance.name + '_' + msg.topic.replace('/', '_')
candidate_entity = entity_registry.async_get_entity_id('sensor', DOMAIN, entity_id)

if candidate_entity is None:
_LOGGER.info("New device! Let's create sensor for %s", msg.topic)
new_entity = SleepAsAndroidSensor(hass, config_entry, msg.topic)
new_entity.message_received(msg, True)
async_add_entities([new_entity])

sub_state = await subscription.async_subscribe_topics(
hass,
sub_state,
{
"state_topic": {
"topic": root_topic + '/+',
"msg_callback": message_received,
"qos": config_entry.data['qos']
}
}
)
if sub_state is not None:
_LOGGER.debug("Subscribing to root topic is done!")

instance: SleepAsAndroidInstance = hass.data[DOMAIN][config_entry.entry_id]
await subscribe_root_topic(instance)
return True


class SleepAsAndroidSensor(Entity):
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
self._hass: HomeAssistant = hass
self._entity_id: ConfigEntry.entry_id = config_entry.entry_id
self._topic: str = config_entry.data['topic']
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry, topic: str):
self._instance: SleepAsAndroidInstance = hass.data[DOMAIN][config_entry.entry_id]

self.hass: HomeAssistant = hass

self._topic: str = topic
self._qos = config_entry.data['qos']
self._name = config_entry.data['name']
self._config = config_entry.data

name = get_name_from_topic(topic)
self._name: str = self._instance.name + '_' + name
self._state: str = STATE_UNKNOWN
self._device_id: str = "unknown"
self._sub_state = None # subscription state

async def async_added_to_hass(self):
await super().async_added_to_hass()
device_registry = await dr.async_get_registry(self._hass)
device_registry = await dr.async_get_registry(self.hass)
device = device_registry.async_get_device(identifiers=self.device_info['identifiers'], connections=set())
_LOGGER.debug("My device id is %s", device.id)
self._device_id = device.id
await self._subscribe_topics()
self.async_write_ha_state()

async def async_will_remove_from_hass(self):
self._sub_state = await subscription.async_unsubscribe_topics(self._hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(self.hass, self._sub_state)

async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
_LOGGER.debug("Subscribing ... ")
_LOGGER.debug("Sensor %s subscribing to topic %s", self.name, self._topic)

@callback
# @log_messages(self.hass, self.entity_id)
def message_received(msg):
"""Handle new MQTT messages."""
new_state = STATE_UNKNOWN
self._sub_state = await subscription.async_subscribe_topics(self.hass, self._sub_state, {
"state_topic": {"topic": self._topic, "msg_callback": self.message_received, "qos": self._qos, }})
_LOGGER.debug("Sensor %s subscribing is done!", self.name)

try:
new_state = json.loads(msg.payload)['event']
except KeyError:
_LOGGER.warning("Got unexpected payload: '%s'", msg.payload)
@callback
def message_received(self, msg, first: bool = False):
"""Handle new MQTT messages."""
new_state = STATE_UNKNOWN

try:
new_state = json.loads(msg.payload)['event']
if self.state != new_state:
payload = {"event": new_state}
_LOGGER.debug("Firing '%s' with payload: '%s'", self.name, payload)
self.hass.bus.fire(self.name, payload)
if new_state in TRIGGERS:
self.hass.bus.async_fire(
DOMAIN + "_event",
{
"device_id": self.device_id,
"type": new_state
}
)
self.hass.bus.async_fire(DOMAIN + "_event", {"device_id": self.device_id, "type": new_state})
else:
_LOGGER.warning("Got %s event, but it is not in TRIGGERS list: will not fire this event for "
"trigger!")

self._state = new_state
self.async_write_ha_state()

self._sub_state = await subscription.async_subscribe_topics(self._hass, self._sub_state, {
"state_topic": {"topic": self._topic, "msg_callback": message_received,
"qos": self._qos, }}, )
_LOGGER.debug("Subscribing ... done!")
except KeyError:
_LOGGER.warning("Got unexpected payload: '%s'", msg.payload)
except json.decoder.JSONDecodeError:
_LOGGER.warning("expected JSON payload. got '%s' instead", msg.payload)
new_state = msg.payload

self._state = new_state
if not first:
self.async_write_ha_state()

@property
def should_poll(self):
Expand Down
2 changes: 2 additions & 0 deletions custom_components/sleep_as_android/translations/en.json
Expand Up @@ -7,6 +7,7 @@
"data": {
"name": "Name for sensor and events ",
"topic": "Topic at MQTT-server with Sleep As Android events. Must be same as in application settings",
"root_topic": "Topic at MQTT-server with Sleep As Android subtopics. All subtopics will be created as independent devices. Must be consistent with application settings",
"qos": "Quality of service for MQTT"
}
}
Expand All @@ -19,6 +20,7 @@
"data": {
"name": "Name for sensor and events ",
"topic": "Topic at MQTT-server with Sleep As Android events. Must be same as in application settings",
"root_topic": "Topic at MQTT-server with Sleep As Android subtopics. All subtopics will be created as independent devices. Must be consistent with application settings",
"qos": "Quality of service for MQTT"
}
}
Expand Down

0 comments on commit 1cd906d

Please sign in to comment.