diff --git a/custom_components/o365/__init__.py b/custom_components/o365/__init__.py
index 98d3d9e..7536481 100644
--- a/custom_components/o365/__init__.py
+++ b/custom_components/o365/__init__.py
@@ -158,7 +158,7 @@ async def _async_setup_account(hass, account_conf, conf_type):
if is_authenticated and permissions and permissions != TOKEN_FILE_MISSING:
check_token = await _async_check_token(hass, account, account_name)
if check_token:
- do_setup(hass, account_conf, account, account_name, conf_type, perms)
+ await do_setup(hass, account_conf, account, account_name, conf_type, perms)
else:
await _async_authorization_repair(
hass,
diff --git a/custom_components/o365/calendar.py b/custom_components/o365/calendar.py
index 6b88f02..61b10f5 100644
--- a/custom_components/o365/calendar.py
+++ b/custom_components/o365/calendar.py
@@ -242,7 +242,6 @@ def _init_data(self, account, calendar_id, entity):
max_results = entity.get(CONF_MAX_RESULTS)
search = entity.get(CONF_SEARCH)
exclude = entity.get(CONF_EXCLUDE)
- # _LOGGER.debug("Initialising calendar: %s", calendar_id)
return O365CalendarData(
account,
self.entity_id,
diff --git a/custom_components/o365/classes/sensorentity.py b/custom_components/o365/classes/sensorentity.py
index f219649..84602af 100644
--- a/custom_components/o365/classes/sensorentity.py
+++ b/custom_components/o365/classes/sensorentity.py
@@ -27,7 +27,7 @@ def name(self):
@property
def entity_key(self):
- """Entity Keyr property."""
+ """Entity Key property."""
return self._entity_id
@property
diff --git a/custom_components/o365/classes/taskssensor.py b/custom_components/o365/classes/taskssensor.py
index f93bab6..9f2f8f9 100644
--- a/custom_components/o365/classes/taskssensor.py
+++ b/custom_components/o365/classes/taskssensor.py
@@ -1,14 +1,16 @@
"""O365 tasks sensors."""
import logging
-from datetime import timedelta
+from datetime import datetime, timedelta
import voluptuous as vol
from homeassistant.components.sensor import SensorEntity
+from homeassistant.const import CONF_ENABLED
from homeassistant.util import dt
from ..const import (
ATTR_ALL_TASKS,
ATTR_COMPLETED,
+ ATTR_CREATED,
ATTR_DESCRIPTION,
ATTR_DUE,
ATTR_OVERDUE_TASKS,
@@ -16,9 +18,10 @@
ATTR_SUBJECT,
ATTR_TASK_ID,
ATTR_TASKS,
- CONF_DUE_HOURS_BACKWARD_TO_GET,
- CONF_DUE_HOURS_FORWARD_TO_GET,
+ CONF_ACCOUNT,
CONF_SHOW_COMPLETED,
+ CONF_TODO_SENSORS,
+ CONF_TRACK_NEW,
DATETIME_FORMAT,
DOMAIN,
EVENT_COMPLETED_TASK,
@@ -31,6 +34,7 @@
PERM_TASKS_READWRITE,
SENSOR_TODO,
)
+from ..utils.filemgmt import update_task_list_file
from .sensorentity import O365Sensor
_LOGGER = logging.getLogger(__name__)
@@ -44,14 +48,10 @@ def __init__(self, coordinator, todo, name, task, config, entity_id, unique_id):
super().__init__(coordinator, config, name, entity_id, SENSOR_TODO, unique_id)
self.todo = todo
self._show_completed = task.get(CONF_SHOW_COMPLETED)
- self.query = self.todo.new_query()
- if not self._show_completed:
- self.query = self.query.on_attribute("status").unequal("completed")
- self.start_offset = task.get(CONF_DUE_HOURS_BACKWARD_TO_GET)
- self.end_offset = task.get(CONF_DUE_HOURS_FORWARD_TO_GET)
self.task_last_created = dt.utcnow() - timedelta(minutes=5)
self.task_last_completed = dt.utcnow() - timedelta(minutes=5)
+ self._zero_date = datetime(1, 1, 1, 0, 0, 0, tzinfo=dt.DEFAULT_TIME_ZONE)
@property
def icon(self):
@@ -96,6 +96,34 @@ def extra_state_attributes(self):
extra_attributes[ATTR_OVERDUE_TASKS] = overdue_tasks
return extra_attributes
+ def _handle_coordinator_update(self) -> None:
+ tasks = self.coordinator.data[self.entity_key][ATTR_TASKS]
+ task_last_completed = self._zero_date
+ task_last_created = self._zero_date
+ for task in tasks:
+ if task.completed and task.completed > self.task_last_completed:
+ self._raise_event_external(
+ EVENT_COMPLETED_TASK,
+ task.task_id,
+ ATTR_COMPLETED,
+ task.completed,
+ )
+ if task.completed > task_last_completed:
+ task_last_completed = task.completed
+ if task.created and task.created > self.task_last_created:
+ self._raise_event_external(
+ EVENT_NEW_TASK, task.task_id, ATTR_CREATED, task.created
+ )
+ if task.created > task_last_created:
+ task_last_created = task.created
+
+ if task_last_completed > self._zero_date:
+ self.task_last_completed = task_last_completed
+ if task_last_created > self._zero_date:
+ self.task_last_created = task_last_created
+
+ self.async_write_ha_state()
+
def new_task(self, subject, description=None, due=None, reminder=None):
"""Create a new task for this task list."""
if not self._validate_task_permissions():
@@ -186,8 +214,41 @@ def _raise_event(self, event_type, task_id):
)
_LOGGER.debug("%s - %s", event_type, task_id)
+ def _raise_event_external(self, event_type, task_id, time_type, task_datetime):
+ self.hass.bus.fire(
+ f"{DOMAIN}_{event_type}",
+ {ATTR_TASK_ID: task_id, time_type: task_datetime, EVENT_HA_EVENT: False},
+ )
+ _LOGGER.debug("%s - %s - %s", event_type, task_id, task_datetime)
+
def _validate_task_permissions(self):
return self._validate_permissions(
PERM_MINIMUM_TASKS_WRITE,
f"Not authorised to create new task - requires permission: {PERM_TASKS_READWRITE}",
)
+
+
+class O365TasksSensorSensorServices:
+ """Sensor Services."""
+
+ def __init__(self, hass):
+ """Initialise the sensor services."""
+ self._hass = hass
+
+ async def async_scan_for_task_lists(self, call): # pylint: disable=unused-argument
+ """Scan for new task lists."""
+ for config in self._hass.data[DOMAIN]:
+ config = self._hass.data[DOMAIN][config]
+ todo_sensor = config.get(CONF_TODO_SENSORS)
+ if todo_sensor and CONF_ACCOUNT in config and todo_sensor.get(CONF_ENABLED):
+ todos = config[CONF_ACCOUNT].tasks()
+
+ todolists = await self._hass.async_add_executor_job(todos.list_folders)
+ track = todo_sensor.get(CONF_TRACK_NEW)
+ for todo in todolists:
+ update_task_list_file(
+ config,
+ todo,
+ self._hass,
+ track,
+ )
diff --git a/custom_components/o365/const.py b/custom_components/o365/const.py
index 1617e4f..aa5961a 100644
--- a/custom_components/o365/const.py
+++ b/custom_components/o365/const.py
@@ -82,6 +82,7 @@ class EventResponse(Enum):
CONF_CLIENT_ID = "client_id"
CONF_CLIENT_SECRET = "client_secret" # nosec
CONF_CONFIG_TYPE = "config_type"
+CONF_COORDINATOR = "coordinator"
CONF_DEVICE_ID = "device_id"
CONF_DOWNLOAD_ATTACHMENTS = "download_attachments"
CONF_DUE_HOURS_BACKWARD_TO_GET = "due_start_offset"
@@ -89,6 +90,8 @@ class EventResponse(Enum):
CONF_EMAIL_SENSORS = "email_sensor"
CONF_ENABLE_UPDATE = "enable_update"
CONF_ENTITIES = "entities"
+CONF_ENTITY_KEY = "entity_key"
+CONF_ENTITY_TYPE = "entity_type"
CONF_EXCLUDE = "exclude"
CONF_FAILED_PERMISSIONS = "failed_permissions"
CONF_GROUPS = "groups"
@@ -98,6 +101,7 @@ class EventResponse(Enum):
CONF_HTML_BODY = "html_body"
CONF_IMPORTANCE = "importance"
CONF_IS_UNREAD = "is_unread"
+CONF_KEYS = "keys"
CONF_MAIL_FOLDER = "folder"
CONF_MAIL_FROM = "from"
CONF_MAX_ITEMS = "max_items"
@@ -110,11 +114,13 @@ class EventResponse(Enum):
CONF_STATUS_SENSORS = "status_sensors"
CONF_SUBJECT_CONTAINS = "subject_contains"
CONF_SUBJECT_IS = "subject_is"
+CONF_TODO = "todo"
CONF_TODO_SENSORS = "todo_sensors"
CONF_TRACK = "track"
CONF_TRACK_NEW_CALENDAR = "track_new_calendar"
CONF_TRACK_NEW = "track_new"
CONF_TASK_LIST_ID = "task_list_id"
+CONF_TASK_LIST = "task_list"
CONF_URL = "url"
CONST_CONFIG_TYPE_DICT = "dict"
CONST_CONFIG_TYPE_LIST = "list"
diff --git a/custom_components/o365/coordinator.py b/custom_components/o365/coordinator.py
new file mode 100644
index 0000000..44dcde3
--- /dev/null
+++ b/custom_components/o365/coordinator.py
@@ -0,0 +1,499 @@
+"""Sensor processing."""
+import functools as ft
+import logging
+from datetime import datetime, timedelta
+from operator import itemgetter
+
+from homeassistant.const import CONF_ENABLED, CONF_NAME, CONF_UNIQUE_ID
+from homeassistant.helpers.entity import async_generate_entity_id
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
+from homeassistant.util import dt
+from requests.exceptions import HTTPError
+
+from .classes.mailsensor import O365AutoReplySensor, O365EmailSensor, O365QuerySensor
+from .classes.taskssensor import O365TasksSensorSensorServices
+from .classes.teamssensor import O365TeamsChatSensor, O365TeamsStatusSensor
+from .const import (
+ ATTR_ATTRIBUTES,
+ ATTR_AUTOREPLIESSETTINGS,
+ ATTR_CHAT_ID,
+ ATTR_CHAT_TYPE,
+ ATTR_CONTENT,
+ ATTR_DATA,
+ ATTR_ERROR,
+ ATTR_FROM_DISPLAY_NAME,
+ ATTR_IMPORTANCE,
+ ATTR_MEMBERS,
+ ATTR_STATE,
+ ATTR_SUBJECT,
+ ATTR_SUMMARY,
+ ATTR_TASK_ID,
+ ATTR_TASKS,
+ ATTR_TOPIC,
+ CONF_ACCOUNT,
+ CONF_ACCOUNT_NAME,
+ CONF_AUTO_REPLY_SENSORS,
+ CONF_CHAT_SENSORS,
+ CONF_DUE_HOURS_BACKWARD_TO_GET,
+ CONF_DUE_HOURS_FORWARD_TO_GET,
+ CONF_EMAIL_SENSORS,
+ CONF_ENABLE_UPDATE,
+ CONF_ENTITY_KEY,
+ CONF_ENTITY_TYPE,
+ CONF_MAIL_FOLDER,
+ CONF_QUERY_SENSORS,
+ CONF_STATUS_SENSORS,
+ CONF_TASK_LIST,
+ CONF_TASK_LIST_ID,
+ CONF_TODO,
+ CONF_TODO_SENSORS,
+ CONF_TRACK,
+ DOMAIN,
+ EVENT_HA_EVENT,
+ LEGACY_ACCOUNT_NAME,
+ SENSOR_AUTO_REPLY,
+ SENSOR_ENTITY_ID_FORMAT,
+ SENSOR_MAIL,
+ SENSOR_TEAMS_CHAT,
+ SENSOR_TEAMS_STATUS,
+ SENSOR_TODO,
+ YAML_TASK_LISTS,
+)
+from .schema import TASK_LIST_SCHEMA
+from .utils.filemgmt import build_config_file_path, build_yaml_filename, load_yaml_file
+from .utils.utils import get_email_attributes
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class O365SensorCordinator(DataUpdateCoordinator):
+ """O365 sensor data update coordinator."""
+
+ def __init__(self, hass, config):
+ """Initialize my coordinator."""
+ super().__init__(
+ hass,
+ _LOGGER,
+ # Name of the data. For logging purposes.
+ name="O365 Sensors",
+ # Polling interval. Will only be polled if there are subscribers.
+ update_interval=timedelta(seconds=30),
+ )
+ self._config = config
+ self._account = config[CONF_ACCOUNT]
+ self._account_name = config[CONF_ACCOUNT_NAME]
+ self._entities = []
+ self._keys = []
+ self._data = {}
+ self._zero_date = datetime(1, 1, 1, 0, 0, 0, tzinfo=dt.DEFAULT_TIME_ZONE)
+ self._chat_members = {}
+
+ async def async_setup_entries(self):
+ """Do the initial setup of the entities."""
+ email_entities = await self._async_email_sensors()
+ query_entities = await self._async_query_sensors()
+ status_entities = self._status_sensors()
+ chat_entities = self._chat_sensors()
+ todo_entities, todo_keys = await self._async_todo_sensors()
+ auto_reply_entities = await self._async_auto_reply_sensors()
+ self._entities = (
+ email_entities
+ + query_entities
+ + status_entities
+ + chat_entities
+ + todo_entities
+ + auto_reply_entities
+ )
+ self._keys = todo_keys
+ return self._entities, self._keys
+
+ async def _async_email_sensors(self):
+ email_sensors = self._config.get(CONF_EMAIL_SENSORS, [])
+ entities = []
+ _LOGGER.debug("Email sensor setup: %s ", self._account_name)
+ for sensor_conf in email_sensors:
+ name = sensor_conf[CONF_NAME]
+ _LOGGER.debug(
+ "Email sensor setup: %s, %s",
+ self._account_name,
+ name,
+ )
+ if mail_folder := await self._async_get_mail_folder(
+ sensor_conf, CONF_EMAIL_SENSORS
+ ):
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{mail_folder.folder_id}_{self._account_name}"
+ emailsensor = O365EmailSensor(
+ self,
+ self._config,
+ sensor_conf,
+ mail_folder,
+ name,
+ entity_id,
+ unique_id,
+ )
+ _LOGGER.debug(
+ "Email sensor added: %s, %s",
+ self._account_name,
+ name,
+ )
+ entities.append(emailsensor)
+ return entities
+
+ async def _async_query_sensors(self):
+ query_sensors = self._config.get(CONF_QUERY_SENSORS, [])
+ entities = []
+ for sensor_conf in query_sensors:
+ if mail_folder := await self._async_get_mail_folder(
+ sensor_conf, CONF_QUERY_SENSORS
+ ):
+ name = sensor_conf.get(CONF_NAME)
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{mail_folder.folder_id}_{self._account_name}"
+ querysensor = O365QuerySensor(
+ self,
+ self._config,
+ sensor_conf,
+ mail_folder,
+ name,
+ entity_id,
+ unique_id,
+ )
+ entities.append(querysensor)
+ return entities
+
+ def _status_sensors(self):
+ status_sensors = self._config.get(CONF_STATUS_SENSORS, [])
+ entities = []
+ for sensor_conf in status_sensors:
+ name = sensor_conf.get(CONF_NAME)
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{name}_{self._account_name}"
+ teams_status_sensor = O365TeamsStatusSensor(
+ self, self._account, name, entity_id, self._config, unique_id
+ )
+ entities.append(teams_status_sensor)
+ return entities
+
+ def _chat_sensors(self):
+ chat_sensors = self._config.get(CONF_CHAT_SENSORS, [])
+ entities = []
+ for sensor_conf in chat_sensors:
+ name = sensor_conf.get(CONF_NAME)
+ enable_update = sensor_conf.get(CONF_ENABLE_UPDATE)
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{name}_{self._account_name}"
+ teams_chat_sensor = O365TeamsChatSensor(
+ self,
+ self._account,
+ name,
+ entity_id,
+ self._config,
+ unique_id,
+ enable_update,
+ )
+ entities.append(teams_chat_sensor)
+ return entities
+
+ async def _async_todo_sensors(self):
+ todo_sensors = self._config.get(CONF_TODO_SENSORS)
+ entities = []
+ keys = []
+ if todo_sensors and todo_sensors.get(CONF_ENABLED):
+ sensor_services = O365TasksSensorSensorServices(self.hass)
+ await sensor_services.async_scan_for_task_lists(None)
+
+ yaml_filename = build_yaml_filename(self._config, YAML_TASK_LISTS)
+ yaml_filepath = build_config_file_path(self.hass, yaml_filename)
+ task_dict = load_yaml_file(
+ yaml_filepath, CONF_TASK_LIST_ID, TASK_LIST_SCHEMA
+ )
+ task_lists = list(task_dict.values())
+ entities, keys = await self._async_todo_entities(task_lists)
+
+ return entities, keys
+
+ async def _async_todo_entities(self, task_lists):
+ entities = []
+ keys = []
+ tasks = self._account.tasks()
+ for tasklist in task_lists:
+ track = tasklist.get(CONF_TRACK)
+ if not track:
+ continue
+
+ task_list_id = tasklist.get(CONF_TASK_LIST_ID)
+ if self._account_name != LEGACY_ACCOUNT_NAME:
+ name = f"{tasklist.get(CONF_NAME)} {self._account_name}"
+ else:
+ name = tasklist.get(CONF_NAME)
+ try:
+ todo = (
+ await self.hass.async_add_executor_job( # pylint: disable=no-member
+ ft.partial(
+ tasks.get_folder,
+ folder_id=task_list_id,
+ )
+ )
+ )
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{task_list_id}_{self._account_name}"
+
+ new_key = {
+ CONF_ENTITY_KEY: entity_id,
+ CONF_UNIQUE_ID: unique_id,
+ CONF_TODO: todo,
+ CONF_NAME: name,
+ CONF_TASK_LIST: tasklist,
+ CONF_ENTITY_TYPE: SENSOR_TODO,
+ }
+
+ keys.append(new_key)
+ except HTTPError:
+ _LOGGER.warning(
+ "Task list not found for: %s - Please remove from O365_tasks_%s.yaml",
+ name,
+ self._account_name,
+ )
+ return entities, keys
+
+ async def _async_auto_reply_sensors(self):
+ auto_reply_sensors = self._config.get(CONF_AUTO_REPLY_SENSORS, [])
+ entities = []
+ for sensor_conf in auto_reply_sensors:
+ name = sensor_conf.get(CONF_NAME)
+ entity_id = self._build_entity_id(name)
+ unique_id = f"{name}_{self._account_name}"
+ auto_reply_sensor = O365AutoReplySensor(
+ self, name, entity_id, self._config, unique_id
+ )
+ entities.append(auto_reply_sensor)
+ return entities
+
+ async def _async_get_mail_folder(self, sensor_conf, sensor_type):
+ """Get the configured folder."""
+ mailbox = self._account.mailbox()
+ _LOGGER.debug("Get mail folder: %s", sensor_conf.get(CONF_NAME))
+ if mail_folder_conf := sensor_conf.get(CONF_MAIL_FOLDER):
+ return await self._async_get_configured_mail_folder(
+ mail_folder_conf, mailbox, sensor_type
+ )
+
+ return mailbox.inbox_folder()
+
+ async def _async_get_configured_mail_folder(
+ self, mail_folder_conf, mailbox, sensor_type
+ ):
+ mail_folder = mailbox
+ for folder in mail_folder_conf.split("/"):
+ mail_folder = await self.hass.async_add_executor_job(
+ ft.partial(
+ mail_folder.get_folder,
+ folder_name=folder,
+ )
+ )
+ if not mail_folder:
+ _LOGGER.error(
+ "Folder - %s - not found from %s config entry - %s - entity not created",
+ folder,
+ sensor_type,
+ mail_folder_conf,
+ )
+ return None
+
+ return mail_folder
+
+ async def _async_update_data(self):
+ _LOGGER.debug("Doing sensor update for: %s", self._account_name)
+ for entity in self._entities:
+ if entity.entity_type == SENSOR_MAIL:
+ await self._async_email_update(entity)
+ elif entity.entity_type == SENSOR_TEAMS_STATUS:
+ await self._async_teams_status_update(entity)
+ elif entity.entity_type == SENSOR_TEAMS_CHAT:
+ await self._async_teams_chat_update(entity)
+ elif entity.entity_type == SENSOR_TODO:
+ await self._async_todos_update(entity)
+ elif entity.entity_type == SENSOR_AUTO_REPLY:
+ await self._async_auto_reply_update(entity)
+
+ for key in self._keys:
+ if key[CONF_ENTITY_TYPE] == SENSOR_TODO:
+ await self._async_todos_update(key)
+
+ return self._data
+
+ async def _async_email_update(self, entity):
+ """Update code."""
+ data = await self.hass.async_add_executor_job( # pylint: disable=no-member
+ ft.partial(
+ entity.mail_folder.get_messages,
+ limit=entity.max_items,
+ query=entity.query,
+ download_attachments=entity.download_attachments,
+ )
+ )
+ attrs = await self.hass.async_add_executor_job( # pylint: disable=no-member
+ self._get_attributes, data, entity
+ )
+ attrs.sort(key=itemgetter("received"), reverse=True)
+ self._data[entity.entity_key] = {
+ ATTR_STATE: len(attrs),
+ ATTR_ATTRIBUTES: {ATTR_DATA: attrs},
+ }
+
+ def _get_attributes(self, data, entity):
+ return [
+ get_email_attributes(x, entity.download_attachments, entity.html_body)
+ for x in data
+ ]
+
+ async def _async_teams_status_update(self, entity):
+ """Update state."""
+ if data := await self.hass.async_add_executor_job(entity.teams.get_my_presence):
+ self._data[entity.entity_key] = {ATTR_STATE: data.activity}
+
+ async def _async_teams_chat_update(self, entity):
+ """Update state."""
+ state = None
+ data = []
+ self._data[entity.entity_key] = {}
+ extra_attributes = {}
+ chats = await self.hass.async_add_executor_job(
+ ft.partial(entity.teams.get_my_chats, limit=20)
+ )
+ for chat in chats:
+ if chat.chat_type == "unknownFutureValue":
+ continue
+ if not state:
+ messages = await self.hass.async_add_executor_job(
+ ft.partial(chat.get_messages, limit=10)
+ )
+ state, extra_attributes = self._process_chat_messages(messages)
+
+ if not entity.enable_update:
+ if state:
+ break
+ continue
+
+ memberlist = await self._async_get_memberlist(chat)
+ chatitems = {
+ ATTR_CHAT_ID: chat.object_id,
+ ATTR_CHAT_TYPE: chat.chat_type,
+ ATTR_MEMBERS: ",".join(memberlist),
+ }
+ if chat.chat_type == "group":
+ chatitems[ATTR_TOPIC] = chat.topic
+
+ data.append(chatitems)
+
+ self._data[entity.entity_key] = (
+ {ATTR_STATE: state} | extra_attributes | {ATTR_DATA: data}
+ )
+
+ def _process_chat_messages(self, messages):
+ state = None
+ extra_attributes = {}
+ for message in messages:
+ if not state and message.content != "":
+ state = message.created_date
+ extra_attributes = {
+ ATTR_FROM_DISPLAY_NAME: message.from_display_name,
+ ATTR_CONTENT: message.content,
+ ATTR_CHAT_ID: message.chat_id,
+ ATTR_IMPORTANCE: message.importance,
+ ATTR_SUBJECT: message.subject,
+ ATTR_SUMMARY: message.summary,
+ }
+ break
+ return state, extra_attributes
+
+ async def _async_get_memberlist(self, chat):
+ if chat.object_id in self._chat_members and chat.chat_type != "oneOnOne":
+ return self._chat_members[chat.object_id]
+ members = await self.hass.async_add_executor_job(chat.get_members)
+ memberlist = [member.display_name for member in members]
+ self._chat_members[chat.object_id] = memberlist
+ return memberlist
+
+ async def _async_todos_update(self, key):
+ """Update state."""
+ entity_key = key["entity_key"]
+ if entity_key in self._data:
+ error = self._data[entity_key][ATTR_ERROR]
+ else:
+ self._data[entity_key] = {ATTR_TASKS: {}, ATTR_STATE: 0}
+ error = False
+ data, error = await self._async_todos_update_query(key, error)
+ if not error:
+ tasks = list(data)
+ self._data[entity_key][ATTR_TASKS] = tasks
+ self._data[entity_key][ATTR_STATE] = len(tasks)
+
+ self._data[entity_key][ATTR_ERROR] = error
+
+ async def _async_todos_update_query(self, key, error):
+ data = None
+ todo = key[CONF_TODO]
+ full_query = self._create_todo_query(key, todo)
+ name = key[CONF_NAME]
+
+ try:
+ data = await self.hass.async_add_executor_job( # pylint: disable=no-member
+ ft.partial(todo.get_tasks, batch=100, query=full_query)
+ )
+ if error:
+ _LOGGER.info("Task list reconnected for: %s", name)
+ error = False
+ except HTTPError:
+ if not error:
+ _LOGGER.error(
+ "Task list not found for: %s - Has it been deleted?",
+ name,
+ )
+ error = True
+
+ return data, error
+
+ def _create_todo_query(self, key, todo):
+ task = key[CONF_TASK_LIST]
+ show_completed = task["show_completed"]
+ query = todo.new_query()
+ if not show_completed:
+ query = query.on_attribute("status").unequal("completed")
+ start_offset = task.get(CONF_DUE_HOURS_BACKWARD_TO_GET)
+ end_offset = task.get(CONF_DUE_HOURS_FORWARD_TO_GET)
+ if start_offset:
+ start = dt.utcnow() + timedelta(hours=start_offset)
+ query.chain("and").on_attribute("due").greater_equal(
+ start.strftime("%Y-%m-%dT%H:%M:%S")
+ )
+ if end_offset:
+ end = dt.utcnow() + timedelta(hours=end_offset)
+ query.chain("and").on_attribute("due").less_equal(
+ end.strftime("%Y-%m-%dT%H:%M:%S")
+ )
+ return query
+
+ async def _async_auto_reply_update(self, entity):
+ """Update state."""
+ if data := await self.hass.async_add_executor_job(entity.mailbox.get_settings):
+ self._data[entity.entity_key] = {
+ ATTR_STATE: data.automaticrepliessettings.status.value,
+ ATTR_AUTOREPLIESSETTINGS: data.automaticrepliessettings,
+ }
+
+ def _build_entity_id(self, name):
+ """Build and entity ID."""
+ return async_generate_entity_id(
+ SENSOR_ENTITY_ID_FORMAT,
+ name,
+ hass=self.hass,
+ )
+
+ def _raise_event(self, event_type, task_id, time_type, task_datetime):
+ self.hass.bus.fire(
+ f"{DOMAIN}_{event_type}",
+ {ATTR_TASK_ID: task_id, time_type: task_datetime, EVENT_HA_EVENT: False},
+ )
+ _LOGGER.debug("%s - %s - %s", event_type, task_id, task_datetime)
diff --git a/custom_components/o365/repairs.py b/custom_components/o365/repairs.py
index ae595f4..5f65d1b 100644
--- a/custom_components/o365/repairs.py
+++ b/custom_components/o365/repairs.py
@@ -153,7 +153,7 @@ async def _async_validate_response(self, user_input):
if not permissions:
errors[CONF_URL] = "minimum_permissions"
- do_setup(
+ await do_setup(
self.hass,
self._conf,
self._account,
diff --git a/custom_components/o365/sensor.py b/custom_components/o365/sensor.py
index cbdb661..53ce9ba 100644
--- a/custom_components/o365/sensor.py
+++ b/custom_components/o365/sensor.py
@@ -1,86 +1,43 @@
"""Sensor processing."""
-import functools as ft
+
import logging
-from copy import deepcopy
-from datetime import datetime, timedelta
-from operator import itemgetter
-from homeassistant.const import CONF_ENABLED, CONF_NAME
+from homeassistant.const import CONF_ENABLED, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.helpers import entity_platform
-from homeassistant.helpers.entity import async_generate_entity_id
-from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
-from homeassistant.util import dt
-from requests.exceptions import HTTPError
-from .classes.mailsensor import O365AutoReplySensor, O365EmailSensor, O365QuerySensor
-from .classes.taskssensor import O365TasksSensor
-from .classes.teamssensor import O365TeamsChatSensor, O365TeamsStatusSensor
+from .classes.taskssensor import O365TasksSensor, O365TasksSensorSensorServices
+from .const import CONF_ACCOUNT_NAME # SENSOR_TODO,
from .const import (
- ATTR_ATTRIBUTES,
- ATTR_AUTOREPLIESSETTINGS,
- ATTR_CHAT_ID,
- ATTR_CHAT_TYPE,
- ATTR_COMPLETED,
- ATTR_CONTENT,
- ATTR_CREATED,
- ATTR_DATA,
- ATTR_ERROR,
- ATTR_FROM_DISPLAY_NAME,
- ATTR_IMPORTANCE,
- ATTR_MEMBERS,
- ATTR_STATE,
- ATTR_SUBJECT,
- ATTR_SUMMARY,
- ATTR_TASK_ID,
- ATTR_TASKS,
- ATTR_TOPIC,
CONF_ACCOUNT,
- CONF_ACCOUNT_NAME,
CONF_AUTO_REPLY_SENSORS,
CONF_CHAT_SENSORS,
- CONF_EMAIL_SENSORS,
+ CONF_COORDINATOR,
CONF_ENABLE_UPDATE,
- CONF_MAIL_FOLDER,
+ CONF_ENTITIES,
+ CONF_ENTITY_KEY,
+ CONF_KEYS,
CONF_PERMISSIONS,
- CONF_QUERY_SENSORS,
- CONF_STATUS_SENSORS,
- CONF_TASK_LIST_ID,
+ CONF_TASK_LIST,
+ CONF_TODO,
CONF_TODO_SENSORS,
- CONF_TRACK,
- CONF_TRACK_NEW,
DOMAIN,
- EVENT_COMPLETED_TASK,
- EVENT_HA_EVENT,
- EVENT_NEW_TASK,
- LEGACY_ACCOUNT_NAME,
PERM_MINIMUM_CHAT_WRITE,
PERM_MINIMUM_MAILBOX_SETTINGS,
PERM_MINIMUM_TASKS_WRITE,
SENSOR_AUTO_REPLY,
- SENSOR_ENTITY_ID_FORMAT,
SENSOR_MAIL,
SENSOR_TEAMS_CHAT,
SENSOR_TEAMS_STATUS,
- SENSOR_TODO,
- YAML_TASK_LISTS,
)
from .schema import (
AUTO_REPLY_SERVICE_DISABLE_SCHEMA,
AUTO_REPLY_SERVICE_ENABLE_SCHEMA,
CHAT_SERVICE_SEND_MESSAGE_SCHEMA,
- TASK_LIST_SCHEMA,
TASK_SERVICE_COMPLETE_SCHEMA,
TASK_SERVICE_DELETE_SCHEMA,
TASK_SERVICE_NEW_SCHEMA,
TASK_SERVICE_UPDATE_SCHEMA,
)
-from .utils.filemgmt import (
- build_config_file_path,
- build_yaml_filename,
- load_yaml_file,
- update_task_list_file,
-)
-from .utils.utils import get_email_attributes
_LOGGER = logging.getLogger(__name__)
@@ -100,441 +57,36 @@ async def async_setup_platform(
if not is_authenticated:
return False
- coordinator = O365SensorCordinator(hass, conf)
- entities = await coordinator.async_setup_entries()
- await coordinator.async_config_entry_first_refresh()
- async_add_entities(entities, False)
- await _async_setup_register_services(hass, conf)
-
- return True
-
-
-class O365SensorCordinator(DataUpdateCoordinator):
- """O365 sensor data update coordinator."""
-
- def __init__(self, hass, config):
- """Initialize my coordinator."""
- super().__init__(
- hass,
- _LOGGER,
- # Name of the data. For logging purposes.
- name="O365 Sensors",
- # Polling interval. Will only be polled if there are subscribers.
- update_interval=timedelta(seconds=30),
- )
- self._config = config
- self._account = config[CONF_ACCOUNT]
- self._account_name = config[CONF_ACCOUNT_NAME]
- self._entities = []
- self._data = {}
- self._zero_date = datetime(1, 1, 1, 0, 0, 0, tzinfo=dt.DEFAULT_TIME_ZONE)
- self._chat_members = {}
-
- async def async_setup_entries(self):
- """Do the initial setup of the entities."""
- email_entities = await self._async_email_sensors()
- query_entities = await self._async_query_sensors()
- status_entities = self._status_sensors()
- chat_entities = self._chat_sensors()
- todo_entities = await self._async_todo_sensors()
- auto_reply_entities = await self._async_auto_reply_sensors()
- self._entities = (
- email_entities
- + query_entities
- + status_entities
- + chat_entities
- + todo_entities
- + auto_reply_entities
- )
- return self._entities
-
- async def _async_email_sensors(self):
- email_sensors = self._config.get(CONF_EMAIL_SENSORS, [])
- entities = []
- _LOGGER.debug("Email sensor setup: %s ", self._account_name)
- for sensor_conf in email_sensors:
- name = sensor_conf[CONF_NAME]
- _LOGGER.debug(
- "Email sensor setup: %s, %s",
- self._account_name,
- name,
- )
- if mail_folder := await self._async_get_mail_folder(
- sensor_conf, CONF_EMAIL_SENSORS
- ):
- entity_id = self._build_entity_id(name)
- unique_id = f"{mail_folder.folder_id}_{self._account_name}"
- emailsensor = O365EmailSensor(
- self,
- self._config,
- sensor_conf,
- mail_folder,
- name,
- entity_id,
- unique_id,
- )
- _LOGGER.debug(
- "Email sensor added: %s, %s",
- self._account_name,
- name,
- )
- entities.append(emailsensor)
- return entities
-
- async def _async_query_sensors(self):
- query_sensors = self._config.get(CONF_QUERY_SENSORS, [])
- entities = []
- for sensor_conf in query_sensors:
- if mail_folder := await self._async_get_mail_folder(
- sensor_conf, CONF_QUERY_SENSORS
- ):
- name = sensor_conf.get(CONF_NAME)
- entity_id = self._build_entity_id(name)
- unique_id = f"{mail_folder.folder_id}_{self._account_name}"
- querysensor = O365QuerySensor(
- self,
- self._config,
- sensor_conf,
- mail_folder,
- name,
- entity_id,
- unique_id,
- )
- entities.append(querysensor)
- return entities
-
- def _status_sensors(self):
- status_sensors = self._config.get(CONF_STATUS_SENSORS, [])
- entities = []
- for sensor_conf in status_sensors:
- name = sensor_conf.get(CONF_NAME)
- entity_id = self._build_entity_id(name)
- unique_id = f"{name}_{self._account_name}"
- teams_status_sensor = O365TeamsStatusSensor(
- self, self._account, name, entity_id, self._config, unique_id
- )
- entities.append(teams_status_sensor)
- return entities
-
- def _chat_sensors(self):
- chat_sensors = self._config.get(CONF_CHAT_SENSORS, [])
- entities = []
- for sensor_conf in chat_sensors:
- name = sensor_conf.get(CONF_NAME)
- enable_update = sensor_conf.get(CONF_ENABLE_UPDATE)
- entity_id = self._build_entity_id(name)
- unique_id = f"{name}_{self._account_name}"
- teams_chat_sensor = O365TeamsChatSensor(
- self,
- self._account,
- name,
- entity_id,
- self._config,
- unique_id,
- enable_update,
- )
- entities.append(teams_chat_sensor)
- return entities
-
- async def _async_todo_sensors(self):
- todo_sensors = self._config.get(CONF_TODO_SENSORS)
- entities = []
- if todo_sensors and todo_sensors.get(CONF_ENABLED):
- sensor_services = SensorServices(self.hass)
- await sensor_services.async_scan_for_task_lists(None)
-
- yaml_filename = build_yaml_filename(self._config, YAML_TASK_LISTS)
- yaml_filepath = build_config_file_path(self.hass, yaml_filename)
- task_dict = load_yaml_file(
- yaml_filepath, CONF_TASK_LIST_ID, TASK_LIST_SCHEMA
- )
- task_lists = list(task_dict.values())
- entities = await self._async_todo_entities(task_lists, self._config)
-
- return entities
-
- async def _async_todo_entities(self, task_lists, config):
- entities = []
- tasks = self._account.tasks()
- for task in task_lists:
- track = task.get(CONF_TRACK)
- if not track:
- continue
-
- task_list_id = task.get(CONF_TASK_LIST_ID)
- if self._account_name != LEGACY_ACCOUNT_NAME:
- name = f"{task.get(CONF_NAME)} {self._account_name}"
- else:
- name = task.get(CONF_NAME)
- try:
- todo = (
- await self.hass.async_add_executor_job( # pylint: disable=no-member
- ft.partial(
- tasks.get_folder,
- folder_id=task_list_id,
- )
- )
- )
- entity_id = self._build_entity_id(name)
- unique_id = f"{task_list_id}_{self._account_name}"
- todo_sensor = O365TasksSensor(
- self, todo, name, task, config, entity_id, unique_id
- )
- entities.append(todo_sensor)
- except HTTPError:
- _LOGGER.warning(
- "Task list not found for: %s - Please remove from O365_tasks_%s.yaml",
- name,
- self._account_name,
- )
- return entities
-
- async def _async_auto_reply_sensors(self):
- auto_reply_sensors = self._config.get(CONF_AUTO_REPLY_SENSORS, [])
- entities = []
- for sensor_conf in auto_reply_sensors:
- name = sensor_conf.get(CONF_NAME)
- entity_id = self._build_entity_id(name)
- unique_id = f"{name}_{self._account_name}"
- auto_reply_sensor = O365AutoReplySensor(
- self, name, entity_id, self._config, unique_id
- )
- entities.append(auto_reply_sensor)
- return entities
-
- async def _async_get_mail_folder(self, sensor_conf, sensor_type):
- """Get the configured folder."""
- mailbox = self._account.mailbox()
- _LOGGER.debug("Get mail folder: %s", sensor_conf.get(CONF_NAME))
- if mail_folder_conf := sensor_conf.get(CONF_MAIL_FOLDER):
- return await self._async_get_configured_mail_folder(
- mail_folder_conf, mailbox, sensor_type
- )
-
- return mailbox.inbox_folder()
-
- async def _async_get_configured_mail_folder(
- self, mail_folder_conf, mailbox, sensor_type
- ):
- mail_folder = mailbox
- for folder in mail_folder_conf.split("/"):
- mail_folder = await self.hass.async_add_executor_job(
- ft.partial(
- mail_folder.get_folder,
- folder_name=folder,
- )
- )
- if not mail_folder:
- _LOGGER.error(
- "Folder - %s - not found from %s config entry - %s - entity not created",
- folder,
- sensor_type,
- mail_folder_conf,
- )
- return None
-
- return mail_folder
-
- async def _async_update_data(self):
- _LOGGER.debug("Doing sensor update for: %s", self._account_name)
- for entity in self._entities:
- if entity.entity_type == SENSOR_MAIL:
- await self._async_email_update(entity)
- elif entity.entity_type == SENSOR_TEAMS_STATUS:
- await self._async_teams_status_update(entity)
- elif entity.entity_type == SENSOR_TEAMS_CHAT:
- await self._async_teams_chat_update(entity)
- elif entity.entity_type == SENSOR_TODO:
- await self._async_todos_update(entity)
- elif entity.entity_type == SENSOR_AUTO_REPLY:
- await self._async_auto_reply_update(entity)
-
- return self._data
-
- async def _async_email_update(self, entity):
- """Update code."""
- data = await self.hass.async_add_executor_job( # pylint: disable=no-member
- ft.partial(
- entity.mail_folder.get_messages,
- limit=entity.max_items,
- query=entity.query,
- download_attachments=entity.download_attachments,
- )
- )
- attrs = await self.hass.async_add_executor_job( # pylint: disable=no-member
- self._get_attributes, data, entity
- )
- attrs.sort(key=itemgetter("received"), reverse=True)
- self._data[entity.entity_key] = {
- ATTR_STATE: len(attrs),
- ATTR_ATTRIBUTES: {ATTR_DATA: attrs},
- }
-
- def _get_attributes(self, data, entity):
- return [
- get_email_attributes(x, entity.download_attachments, entity.html_body)
- for x in data
+ entities = conf[CONF_ENTITIES]
+ sensorentities = [
+ entity
+ for entity in entities
+ if entity.entity_type
+ in [
+ SENSOR_MAIL,
+ SENSOR_TEAMS_STATUS,
+ SENSOR_TEAMS_CHAT,
+ # SENSOR_TODO,
+ SENSOR_AUTO_REPLY,
]
-
- async def _async_teams_status_update(self, entity):
- """Update state."""
- if data := await self.hass.async_add_executor_job(entity.teams.get_my_presence):
- self._data[entity.entity_key] = {ATTR_STATE: data.activity}
-
- async def _async_teams_chat_update(self, entity):
- """Update state."""
- state = None
- data = []
- self._data[entity.entity_key] = {}
- extra_attributes = {}
- chats = await self.hass.async_add_executor_job(
- ft.partial(entity.teams.get_my_chats, limit=20)
- )
- for chat in chats:
- if chat.chat_type == "unknownFutureValue":
- continue
- if not state:
- messages = await self.hass.async_add_executor_job(
- ft.partial(chat.get_messages, limit=10)
- )
- state, extra_attributes = self._process_chat_messages(messages)
-
- if not entity.enable_update:
- if state:
- break
- continue
-
- memberlist = await self._async_get_memberlist(chat)
- chatitems = {
- ATTR_CHAT_ID: chat.object_id,
- ATTR_CHAT_TYPE: chat.chat_type,
- ATTR_MEMBERS: ",".join(memberlist),
- }
- if chat.chat_type == "group":
- chatitems[ATTR_TOPIC] = chat.topic
-
- data.append(chatitems)
-
- self._data[entity.entity_key] = (
- {ATTR_STATE: state} | extra_attributes | {ATTR_DATA: data}
- )
-
- def _process_chat_messages(self, messages):
- state = None
- extra_attributes = {}
- for message in messages:
- if not state and message.content != "":
- state = message.created_date
- extra_attributes = {
- ATTR_FROM_DISPLAY_NAME: message.from_display_name,
- ATTR_CONTENT: message.content,
- ATTR_CHAT_ID: message.chat_id,
- ATTR_IMPORTANCE: message.importance,
- ATTR_SUBJECT: message.subject,
- ATTR_SUMMARY: message.summary,
- }
- break
- return state, extra_attributes
-
- async def _async_get_memberlist(self, chat):
- if chat.object_id in self._chat_members and chat.chat_type != "oneOnOne":
- return self._chat_members[chat.object_id]
- members = await self.hass.async_add_executor_job(chat.get_members)
- memberlist = [member.display_name for member in members]
- self._chat_members[chat.object_id] = memberlist
- return memberlist
-
- async def _async_todos_update(self, entity):
- """Update state."""
- if entity.entity_key in self._data:
- error = self._data[entity.entity_key][ATTR_ERROR]
- else:
- self._data[entity.entity_key] = {ATTR_TASKS: {}, ATTR_STATE: 0}
- error = False
- data, error = await self._async_todos_update_query(entity, error)
- if not error:
- tasks = list(data)
- self._data[entity.entity_key][ATTR_TASKS] = tasks
- self._data[entity.entity_key][ATTR_STATE] = len(tasks)
- task_last_completed = self._zero_date
- task_last_created = self._zero_date
- for task in tasks:
- if task.completed and task.completed > entity.task_last_completed:
- self._raise_event(
- EVENT_COMPLETED_TASK,
- task.task_id,
- ATTR_COMPLETED,
- task.completed,
- )
- if task.completed > task_last_completed:
- task_last_completed = task.completed
- if task.created and task.created > entity.task_last_created:
- self._raise_event(
- EVENT_NEW_TASK, task.task_id, ATTR_CREATED, task.created
- )
- if task.created > task_last_created:
- task_last_created = task.created
-
- if task_last_completed > self._zero_date:
- entity.task_last_completed = task_last_completed
- if task_last_created > self._zero_date:
- entity.task_last_created = task_last_created
-
- self._data[entity.entity_key][ATTR_ERROR] = error
-
- async def _async_todos_update_query(self, entity, error):
- data = None
- full_query = deepcopy(entity.query)
- if entity.start_offset:
- start = dt.utcnow() + timedelta(hours=entity.start_offset)
- full_query.chain("and").on_attribute("due").greater_equal(
- start.strftime("%Y-%m-%dT%H:%M:%S")
- )
- if entity.end_offset:
- end = dt.utcnow() + timedelta(hours=entity.end_offset)
- full_query.chain("and").on_attribute("due").less_equal(
- end.strftime("%Y-%m-%dT%H:%M:%S")
- )
-
- try:
- data = await self.hass.async_add_executor_job( # pylint: disable=no-member
- ft.partial(entity.todo.get_tasks, batch=100, query=full_query)
- )
- if error:
- _LOGGER.info("Task list reconnected for: %s", entity.name)
- error = False
- except HTTPError:
- if not error:
- _LOGGER.error(
- "Task list not found for: %s - Has it been deleted?",
- entity.name,
- )
- error = True
-
- return data, error
-
- async def _async_auto_reply_update(self, entity):
- """Update state."""
- if data := await self.hass.async_add_executor_job(entity.mailbox.get_settings):
- self._data[entity.entity_key] = {
- ATTR_STATE: data.automaticrepliessettings.status.value,
- ATTR_AUTOREPLIESSETTINGS: data.automaticrepliessettings,
- }
-
- def _build_entity_id(self, name):
- """Build and entity ID."""
- return async_generate_entity_id(
- SENSOR_ENTITY_ID_FORMAT,
- name,
- hass=self.hass,
+ ]
+ coordinator = conf[CONF_COORDINATOR]
+ sensorentities.extend(
+ O365TasksSensor(
+ coordinator,
+ key[CONF_TODO],
+ key[CONF_NAME],
+ key[CONF_TASK_LIST],
+ config,
+ key[CONF_ENTITY_KEY],
+ key[CONF_UNIQUE_ID],
)
+ for key in conf[CONF_KEYS]
+ )
+ async_add_entities(sensorentities, False)
+ await _async_setup_register_services(hass, conf)
- def _raise_event(self, event_type, task_id, time_type, task_datetime):
- self.hass.bus.fire(
- f"{DOMAIN}_{event_type}",
- {ATTR_TASK_ID: task_id, time_type: task_datetime, EVENT_HA_EVENT: False},
- )
- _LOGGER.debug("%s - %s - %s", event_type, task_id, task_datetime)
+ return True
async def _async_setup_register_services(hass, config):
@@ -553,7 +105,7 @@ async def _async_setup_task_services(hass, config, perms):
):
return
- sensor_services = SensorServices(hass)
+ sensor_services = O365TasksSensorSensorServices(hass)
hass.services.async_register(
DOMAIN, "scan_for_task_lists", sensor_services.async_scan_for_task_lists
)
@@ -618,29 +170,3 @@ async def _async_setup_mailbox_services(config, perms):
AUTO_REPLY_SERVICE_DISABLE_SCHEMA,
"auto_reply_disable",
)
-
-
-class SensorServices:
- """Sensor Services."""
-
- def __init__(self, hass):
- """Initialise the sensor services."""
- self._hass = hass
-
- async def async_scan_for_task_lists(self, call): # pylint: disable=unused-argument
- """Scan for new task lists."""
- for config in self._hass.data[DOMAIN]:
- config = self._hass.data[DOMAIN][config]
- todo_sensor = config.get(CONF_TODO_SENSORS)
- if todo_sensor and CONF_ACCOUNT in config and todo_sensor.get(CONF_ENABLED):
- todos = config[CONF_ACCOUNT].tasks()
-
- todolists = await self._hass.async_add_executor_job(todos.list_folders)
- track = todo_sensor.get(CONF_TRACK_NEW)
- for todo in todolists:
- update_task_list_file(
- config,
- todo,
- self._hass,
- track,
- )
diff --git a/custom_components/o365/setup.py b/custom_components/o365/setup.py
index 2ed92df..16587ff 100644
--- a/custom_components/o365/setup.py
+++ b/custom_components/o365/setup.py
@@ -9,8 +9,11 @@
CONF_AUTO_REPLY_SENSORS,
CONF_CHAT_SENSORS,
CONF_CONFIG_TYPE,
+ CONF_COORDINATOR,
CONF_EMAIL_SENSORS,
CONF_ENABLE_UPDATE,
+ CONF_ENTITIES,
+ CONF_KEYS,
CONF_PERMISSIONS,
CONF_QUERY_SENSORS,
CONF_STATUS_SENSORS,
@@ -18,9 +21,10 @@
CONF_TRACK_NEW_CALENDAR,
DOMAIN,
)
+from .coordinator import O365SensorCordinator
-def do_setup(hass, config, account, account_name, conf_type, perms):
+async def do_setup(hass, config, account, account_name, conf_type, perms):
"""Run the setup after we have everything configured."""
email_sensors = config.get(CONF_EMAIL_SENSORS, [])
query_sensors = config.get(CONF_QUERY_SENSORS, [])
@@ -48,6 +52,13 @@ def do_setup(hass, config, account, account_name, conf_type, perms):
hass.data[DOMAIN] = {}
hass.data[DOMAIN][account_name] = account_config
+ coordinator = O365SensorCordinator(hass, account_config)
+ entities, keys = await coordinator.async_setup_entries()
+ await coordinator.async_config_entry_first_refresh()
+ hass.data[DOMAIN][account_name][CONF_ENTITIES] = entities
+ hass.data[DOMAIN][account_name][CONF_KEYS] = keys
+ hass.data[DOMAIN][account_name][CONF_COORDINATOR] = coordinator
+
_load_platforms(hass, account_name, config, account_config)