├── hacs.json ├── custom_components └── microsoft_graph │ ├── const.py │ ├── manifest.json │ ├── translations │ ├── en.json │ └── pt-BR.json │ ├── strings.json │ ├── base_sensor.py │ ├── api.py │ ├── config_flow.py │ ├── sensor.py │ └── __init__.py ├── .github └── workflows │ └── HACS.yml ├── info.md └── README.md /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Microsoft Graph", 3 | "country": "GB", 4 | "render_readme": true, 5 | } 6 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/const.py: -------------------------------------------------------------------------------- 1 | """Constants for the Microsoft Graph integration.""" 2 | 3 | DOMAIN = "microsoft_graph" 4 | 5 | OAUTH2_AUTHORIZE = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" 6 | OAUTH2_TOKEN = "https://login.microsoftonline.com/common/oauth2/v2.0/token" 7 | DEFAULT_SCOPES = ["https://graph.microsoft.com/.default", "offline_access"] 8 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "microsoft_graph", 3 | "version": "2.0.0", 4 | "name": "Microsoft Graph", 5 | "config_flow": true, 6 | "documentation": "https://github.com/geoffreylagaisse/Hass-Microsoft-Graph", 7 | "issue_tracker": "https://github.com/geoffreylagaisse/Hass-Microsoft-Graph/issues", 8 | "requirements": [ 9 | "ha-graphapi==0.0.20" 10 | ], 11 | "dependencies": [ 12 | "http", "auth" 13 | ], 14 | "codeowners": [ 15 | "@jlweston", "@geoffreylagaisse" 16 | ], 17 | "iot_class": "cloud_polling" 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/HACS.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "HACS validate" 3 | 4 | on: 5 | push: 6 | pull_request: 7 | branches: ["main"] 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | validate-hassfest: 13 | name: "Hassfest validation" 14 | runs-on: "ubuntu-latest" 15 | steps: 16 | - name: "checkout" 17 | uses: "actions/checkout@v2" 18 | - name: "validation" 19 | uses: "home-assistant/actions/hassfest@master" 20 | 21 | validate-hacs: 22 | name: "HACS validation" 23 | runs-on: "ubuntu-latest" 24 | steps: 25 | - name: HACS Action 26 | uses: "hacs/action@main" 27 | with: 28 | category: "integration" 29 | ignore: "hacsjson brands" 30 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "authorize_url_timeout": "Timeout generating authorize URL.", 5 | "missing_configuration": "The component is not configured. Please follow the documentation.", 6 | "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})" 7 | }, 8 | "create_entry": { 9 | "default": "Successfully authenticated" 10 | }, 11 | "step": { 12 | "pick_implementation": { 13 | "title": "Pick Authentication Method" 14 | }, 15 | "user": { 16 | "data": { 17 | "client_id": "Client ID", 18 | "client_secret": "Client Secret" 19 | }, 20 | "description": "Do you want to start set up?" 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /custom_components/microsoft_graph/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "authorize_url_timeout": "Tempo limite gerando URL de autorização.", 5 | "missing_configuration": "O componente não está configurado. Por favor, siga a documentação.", 6 | "no_url_available": "Nenhuma URL disponível. Para obter informações sobre este erro, [verifique a seção de ajuda]({docs_url})" 7 | }, 8 | "create_entry": { 9 | "default": "Autenticado com sucesso" 10 | }, 11 | "step": { 12 | "pick_implementation": { 13 | "title": "Escolha o método de autenticação" 14 | }, 15 | "user": { 16 | "data": { 17 | "client_id": "ID do Cliente", 18 | "client_secret": "Secret do Cliente" 19 | }, 20 | "description": "Deseja iniciar a configuração?" 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /custom_components/microsoft_graph/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "pick_implementation": { 5 | "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" 6 | }, 7 | "user": { 8 | "description": "[%key:common::config_flow:step::user::description%]", 9 | "data": { 10 | "client_id": "[%key:common::config_flow::step::user::data::client_id%]", 11 | "client_secret": "[%key:common::config_flow::step::user::data::client_secret%]" 12 | } 13 | } 14 | }, 15 | "abort": { 16 | "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", 17 | "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", 18 | "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" 19 | }, 20 | "create_entry": { 21 | "default": "[%key:common::config_flow::create_entry::authenticated%]" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/base_sensor.py: -------------------------------------------------------------------------------- 1 | """Base Sensor for the Microsoft Graph Integration.""" 2 | from typing import Optional 3 | 4 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 5 | from homeassistant.helpers.device_registry import DeviceEntryType 6 | 7 | from . import GraphUpdateCoordinator 8 | from .const import DOMAIN 9 | 10 | 11 | class GraphBaseSensorEntity(CoordinatorEntity): 12 | """Base Sensor for the Microsoft Graph Integration.""" 13 | 14 | def __init__(self, coordinator: GraphUpdateCoordinator, uuid: str, attribute: str): 15 | """Initialize Microsoft Graph sensor.""" 16 | super().__init__(coordinator) 17 | self.uuid = uuid 18 | self.attribute = attribute 19 | 20 | @property 21 | def unique_id(self) -> str: 22 | """Return a unique, Home Assistant friendly identifier for this entity.""" 23 | return f"{self.attribute}" 24 | 25 | @property 26 | def data(self) -> Optional[dict]: 27 | """Return coordinator data for this user.""" 28 | return self.coordinator.data.presence.get(self.uuid) 29 | 30 | @property 31 | def name(self) -> str: 32 | """Return the name of the sensor.""" 33 | if not self.data: 34 | return None 35 | 36 | return " ".join([part.title() for part in self.attribute.split("_")]) 37 | 38 | @property 39 | def entity_registry_enabled_default(self) -> bool: 40 | """Return if the entity should be enabled when first added to the entity registry.""" 41 | return True 42 | 43 | @property 44 | def device_info(self): 45 | """Return a device description for device registry.""" 46 | return { 47 | "identifiers": {(DOMAIN, "microsoft_graph")}, 48 | "name": "Microsoft Graph", 49 | "manufacturer": "Microsoft", 50 | "model": "Microsoft Graph", 51 | "entry_type": DeviceEntryType.SERVICE, 52 | } 53 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/api.py: -------------------------------------------------------------------------------- 1 | """API for Microsoft Graph bound to Home Assistant OAuth.""" 2 | from asyncio import run_coroutine_threadsafe 3 | 4 | from aiohttp import ClientSession 5 | from hagraph.api.auth.manager import AbstractAuth 6 | 7 | from homeassistant import config_entries, core 8 | from homeassistant.helpers import config_entry_oauth2_flow 9 | 10 | 11 | class ConfigEntryAuth(AbstractAuth): 12 | """Provide Microsoft Graph authentication tied to an OAuth2 based config entry.""" 13 | 14 | def __init__( 15 | self, 16 | hass: core.HomeAssistant, 17 | config_entry: config_entries.ConfigEntry, 18 | implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation, 19 | ): 20 | """Initialize Microsoft Graph Auth.""" 21 | self.hass = hass 22 | self.config_entry = config_entry 23 | self.session = config_entry_oauth2_flow.OAuth2Session( 24 | hass, config_entry, implementation 25 | ) 26 | super().__init__(self.session.token) 27 | 28 | def refresh_tokens(self) -> str: 29 | """Refresh and return new Microsoft Graph tokens using Home Assistant OAuth2 session.""" 30 | run_coroutine_threadsafe( 31 | self.session.async_ensure_token_valid(), self.hass.loop 32 | ).result() 33 | 34 | return self.session.token["access_token"] 35 | 36 | 37 | class AsyncConfigEntryAuth(AbstractAuth): 38 | """Provide Microsoft Graph authentication tied to an OAuth2 based config entry.""" 39 | 40 | def __init__( 41 | self, 42 | websession: ClientSession, 43 | oauth_session: config_entry_oauth2_flow.OAuth2Session, 44 | ): 45 | """Initialize Microsoft Graph auth.""" 46 | super().__init__(websession) 47 | self._oauth_session = oauth_session 48 | 49 | async def async_get_access_token(self) -> str: 50 | """Return a valid access token.""" 51 | if not self._oauth_session.valid_token: 52 | await self._oauth_session.async_ensure_token_valid() 53 | 54 | return self._oauth_session.token["access_token"] 55 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for Microsoft Graph.""" 2 | import logging 3 | 4 | from homeassistant import config_entries 5 | from homeassistant.helpers import config_entry_oauth2_flow 6 | from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET 7 | import voluptuous as vol 8 | 9 | from .const import DEFAULT_SCOPES, DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | class OAuth2FlowHandler( 15 | config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN 16 | ): 17 | """Config flow to handle Microsoft Graph OAuth2 authentication.""" 18 | 19 | DOMAIN = DOMAIN 20 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 21 | 22 | @property 23 | def logger(self) -> logging.Logger: 24 | """Return logger.""" 25 | return logging.getLogger(__name__) 26 | 27 | @property 28 | def extra_authorize_data(self) -> dict: 29 | """Extra data that needs to be appended to the authorize url.""" 30 | return { 31 | "scope": " ".join(DEFAULT_SCOPES), 32 | } 33 | 34 | async def async_oauth_create_entry(self, data: dict): 35 | """Create an entry for the flow. 36 | 37 | Ok to override if you want to fetch extra info or even add another step. 38 | """ 39 | data[CONF_CLIENT_ID] = self.hass.data[DOMAIN][CONF_CLIENT_ID] 40 | data[CONF_CLIENT_SECRET] = self.hass.data[DOMAIN][CONF_CLIENT_SECRET] 41 | 42 | return self.async_create_entry(title=self.flow_impl.name, data=data) 43 | 44 | async def async_step_user(self, user_input=None): 45 | """Handle the initial step.""" 46 | errors = {} 47 | if user_input is not None: 48 | await self.async_set_unique_id(DOMAIN) 49 | self._abort_if_unique_id_configured() 50 | 51 | self.hass.data[DOMAIN] = { 52 | CONF_CLIENT_ID: user_input[CONF_CLIENT_ID], 53 | CONF_CLIENT_SECRET: user_input[CONF_CLIENT_SECRET], 54 | } 55 | 56 | self.async_register_implementation( 57 | self.hass, 58 | config_entry_oauth2_flow.LocalOAuth2Implementation( 59 | self.hass, 60 | DOMAIN, 61 | user_input[CONF_CLIENT_ID], 62 | user_input[CONF_CLIENT_SECRET], 63 | OAUTH2_AUTHORIZE, 64 | OAUTH2_TOKEN, 65 | ), 66 | ) 67 | user_input["implementation"] = DOMAIN 68 | 69 | flow = await self.async_step_pick_implementation(user_input) 70 | return flow 71 | 72 | return self.async_show_form( 73 | step_id="user", 74 | data_schema=vol.Schema( 75 | { 76 | vol.Required(CONF_CLIENT_ID): str, 77 | vol.Required(CONF_CLIENT_SECRET): str, 78 | } 79 | ), 80 | errors=errors, 81 | ) 82 | -------------------------------------------------------------------------------- /custom_components/microsoft_graph/sensor.py: -------------------------------------------------------------------------------- 1 | """Microsoft Graph sensors.""" 2 | from functools import partial 3 | 4 | from homeassistant.core import callback 5 | from homeassistant.helpers.entity_registry import ( 6 | async_get as async_get_entity_registry, 7 | ) 8 | from homeassistant.core import HomeAssistant 9 | 10 | from . import GraphUpdateCoordinator 11 | from .base_sensor import GraphBaseSensorEntity 12 | from .const import DOMAIN 13 | 14 | MICROSOFT_TEAMS_ATTRIBUTES = ["availability", "activity"] 15 | 16 | 17 | async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities): 18 | """Set up Graph presence.""" 19 | coordinator: GraphUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id][ 20 | "coordinator" 21 | ] 22 | 23 | update_teams_presence = partial( 24 | async_update_teams_presence, coordinator, {}, async_add_entities 25 | ) 26 | 27 | unsub = coordinator.async_add_listener(update_teams_presence) 28 | hass.data[DOMAIN][config_entry.entry_id]["sensor_unsub"] = unsub 29 | update_teams_presence() 30 | 31 | 32 | class TeamsPresenceSensorEntity(GraphBaseSensorEntity): 33 | """Representation of a Microsoft Graph presence state.""" 34 | 35 | @property 36 | def unique_id(self) -> str: 37 | """Return a unique, Home Assistant friendly identifier for this entity.""" 38 | return f"ms_teams_{self.attribute}" 39 | 40 | @property 41 | def name(self) -> str: 42 | """Return the name of the sensor.""" 43 | if not self.data: 44 | return None 45 | 46 | return f"Microsoft Teams {' '.join([part.title() for part in self.attribute.split('_')])}" 47 | 48 | @property 49 | def state(self): 50 | """Return the state of the requested attribute.""" 51 | if not self.coordinator.last_update_success: 52 | return None 53 | 54 | return getattr(self.data, self.attribute, None) 55 | 56 | 57 | @callback 58 | def async_update_teams_presence( 59 | coordinator: GraphUpdateCoordinator, 60 | current: dict[str, list[TeamsPresenceSensorEntity]], 61 | async_add_entities, 62 | ) -> None: 63 | """Update teams_presence.""" 64 | new_ids = set(coordinator.data.presence) 65 | current_ids = set(current) 66 | 67 | # Process new sensors, add them to Home Assistant 68 | new_entities = [] 69 | for uuid in new_ids - current_ids: 70 | current[uuid] = [ 71 | TeamsPresenceSensorEntity(coordinator, uuid, attribute) 72 | for attribute in MICROSOFT_TEAMS_ATTRIBUTES 73 | ] 74 | new_entities = new_entities + current[uuid] 75 | 76 | if new_entities: 77 | async_add_entities(new_entities) 78 | 79 | # Process deleted sensors, remove them from Home Assistant 80 | for uuid in current_ids - new_ids: 81 | coordinator.hass.async_create_task( 82 | async_remove_entities(uuid, coordinator, current) 83 | ) 84 | 85 | 86 | async def async_remove_entities( 87 | uuid: str, 88 | coordinator: GraphUpdateCoordinator, 89 | current: dict[str, TeamsPresenceSensorEntity], 90 | ) -> None: 91 | """Remove sensors from Home Assistant.""" 92 | registry = await async_get_entity_registry(coordinator.hass) 93 | entities = current[uuid] 94 | for entity in entities: 95 | if entity.entity_id in registry.entities: 96 | registry.async_remove(entity.entity_id) 97 | del current[uuid] 98 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Custom Components 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 4 | [![Discord](https://img.shields.io/discord/330944238910963714?label=discord)](https://discord.gg/b25Yyh24) 5 | 6 | Custom Components for [Home Assistant](http://www.home-assistant.io). 7 | 8 | ## Microsoft Graph API Component 9 | 10 | Component to interface with Microsoft's Graph API on your organization's tenant. 11 | Currently only detects the authorized user's Microsoft Teams availability and activity but the possibilities are almost endless. 12 | 13 | The Microsoft Graph integration allows Home Assistant to create sensors from various properties of your Office 365 tenant via Microsoft's Graph API. All queries are performed using the authenticated user's account and depend on the permissions granted when creating the Azure AD application. 14 | 15 | Home Assistant authenticates with Microsoft through OAuth2. Set up your credentials by completing the steps in [Manual Configuration](#manual-configuration) and then add the integration in **Configuration -> Integrations -> Microsoft Graph**. Ensure you log in using a Microsoft account that belongs to your organization's tenant. 16 | 17 | - [Home Assistant Custom Components](#home-assistant-custom-components) 18 | - [Microsoft Graph API Component](#microsoft-graph-api-component) 19 | - [Manual Configuration](#manual-configuration) 20 | - [Sensor](#sensor) 21 | - [Microsoft Teams](#microsoft-teams) 22 | 23 | ### Manual Configuration 24 | 25 | You will need to register an application in Azure AD and retrieve the client ID and client secret: 26 | 27 | - Register a new application in [Azure AD](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) 28 | - Name your app 29 | - Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant)" under supported account types 30 | - For Redirect URI, add: `https:///auth/external/callback` 31 | - Copy your Application (client) ID for later use 32 | - On the App Page, navigate to "Certificates & secrets" 33 | - Generate a new client secret and *save for later use* (you will *not* be able to see this again) 34 | 35 | Then set the relevant permissions on the application on the API Permissions page. All of the following are required to function correctly: 36 | 37 | - Presence.Read 38 | - Presence.Read.All 39 | - User.Read 40 | - User.ReadBasic.All 41 | 42 | Add the client id and secret in the configuration flow through **Configuration -> Integrations -> Microsoft Graph**: 43 | 44 | And Finish setup in the UI through. 45 | 46 | ### Sensor 47 | 48 | The Microsoft Graph sensor platform automatically tracks various resources from your Office 365 tenant. 49 | 50 | #### Microsoft Teams 51 | 52 | There are 2 sensors that are added, both of which are enabled by default. 53 | 54 | | Entity ID | Default | Description | 55 | | ---------------------------------| ------ | -----------------------------------------------------------------------------| 56 | | `sensor.ms_teams_availability` | Enabled | Shows your availability (e.g. Available, AvailableIdle, Away). | 57 | | `sensor.ms_teams_activity` | Enabled | Shows your activity (e.g. InACall, InAConferenceCall, Inactive, InAMeeting). | 58 | 59 | See possible availability and activity values [here](https://docs.microsoft.com/en-us/graph/api/resources/presence?view=graph-rest-beta#properties). -------------------------------------------------------------------------------- /custom_components/microsoft_graph/__init__.py: -------------------------------------------------------------------------------- 1 | """The Microsoft Graph integration.""" 2 | import asyncio 3 | from dataclasses import dataclass 4 | from datetime import timedelta 5 | import logging 6 | 7 | from hagraph.api.client import GraphApiClient 8 | from hagraph.api.provider.presence.models import PresenceResponse 9 | import voluptuous as vol 10 | 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers import ( 14 | aiohttp_client, 15 | config_entry_oauth2_flow, 16 | ) 17 | from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET 18 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 19 | 20 | from . import api, config_flow 21 | from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | PLATFORMS = ["sensor"] 26 | 27 | 28 | async def async_setup(hass: HomeAssistant, config: dict): 29 | """Set up the Microsoft Graph component.""" 30 | hass.data.setdefault(DOMAIN, {}) 31 | 32 | return True 33 | 34 | 35 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 36 | """Set up Microsoft Graph from a config entry.""" 37 | config_flow.OAuth2FlowHandler.async_register_implementation( 38 | hass, 39 | config_entry_oauth2_flow.LocalOAuth2Implementation( 40 | hass, 41 | DOMAIN, 42 | entry.data[CONF_CLIENT_ID], 43 | entry.data[CONF_CLIENT_SECRET], 44 | OAUTH2_AUTHORIZE, 45 | OAUTH2_TOKEN, 46 | ), 47 | ) 48 | implementation = ( 49 | await config_entry_oauth2_flow.async_get_config_entry_implementation( 50 | hass, entry 51 | ) 52 | ) 53 | 54 | session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) 55 | auth = api.AsyncConfigEntryAuth( 56 | aiohttp_client.async_get_clientsession(hass), session 57 | ) 58 | 59 | client = GraphApiClient(auth) 60 | 61 | coordinator = GraphUpdateCoordinator(hass, client) 62 | await coordinator.async_refresh() 63 | 64 | hass.data[DOMAIN][entry.entry_id] = { 65 | "client": GraphApiClient(auth), 66 | "coordinator": coordinator, 67 | } 68 | 69 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 70 | 71 | return True 72 | 73 | 74 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): 75 | """Unload a config entry.""" 76 | unload_ok = all( 77 | await asyncio.gather( 78 | *[ 79 | hass.config_entries.async_forward_entry_unload(entry, component) 80 | for component in PLATFORMS 81 | ] 82 | ) 83 | ) 84 | if unload_ok: 85 | hass.data[DOMAIN][entry.entry_id]["sensor_unsub"]() 86 | hass.data[DOMAIN].pop(entry.entry_id) 87 | 88 | return unload_ok 89 | 90 | 91 | @dataclass 92 | class PresenceData: 93 | """Microsoft Graph user presence data.""" 94 | 95 | uuid: str 96 | availability: str 97 | activity: str 98 | 99 | 100 | @dataclass 101 | class GraphData: 102 | """Graph dataclass for update coordinator.""" 103 | 104 | presence: dict[str, PresenceData] 105 | 106 | 107 | class GraphUpdateCoordinator(DataUpdateCoordinator): 108 | """Store Graph Status.""" 109 | 110 | def __init__( 111 | self, 112 | hass: HomeAssistant, 113 | client: GraphApiClient, 114 | ) -> None: 115 | """Initialize.""" 116 | super().__init__( 117 | hass, 118 | _LOGGER, 119 | name=DOMAIN, 120 | update_interval=timedelta(seconds=5), 121 | ) 122 | self.data: GraphData = GraphData({}) 123 | self.client: GraphApiClient = client 124 | 125 | async def _async_update_data(self) -> GraphData: 126 | """Fetch the latest data.""" 127 | 128 | # Update user presence 129 | presence_data = {} 130 | my_presence = await self.client.presence.get_presence() 131 | presence_data[my_presence.id] = _build_presence_data(my_presence) 132 | 133 | _LOGGER.debug("Microsoft Graph presence_data: %s", presence_data) 134 | 135 | return GraphData(presence_data) 136 | 137 | 138 | def _build_presence_data(person: PresenceResponse) -> PresenceData: 139 | """Build presence data from a person.""" 140 | 141 | return PresenceData( 142 | uuid=person.id, 143 | availability=person.availability, 144 | activity=person.activity, 145 | ) 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Custom Components 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/hacs/integration) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 4 | [![Discord](https://img.shields.io/discord/330944238910963714?label=discord)](https://discord.gg/AnNenrd2) 5 | 6 | Custom Component for [Home Assistant](http://www.home-assistant.io). 7 | 8 | ## Microsoft Graph API Component 9 | 10 | Component to interface with Microsoft's Graph API on your organization's tenant. 11 | Currently only detects the authorized user's Microsoft Teams availability and activity but the possibilities are almost endless. 12 | 13 | The Microsoft Graph integration allows Home Assistant to create sensors from various properties of your Office 365 tenant via Microsoft's Graph API. All queries are performed using the authenticated user's account and depend on the permissions granted when creating the Azure AD application. 14 | 15 | Home Assistant authenticates with Microsoft through OAuth2. Set up your credentials by completing the steps in [Manual Configuration](#manual-configuration) and then add the integration in **Configuration -> Integrations -> Microsoft Graph**. Ensure you log in using a Microsoft account that belongs to your organization's tenant. 16 | 17 | - [Home Assistant Custom Components](#home-assistant-custom-components) 18 | - [Microsoft Graph API Component](#microsoft-graph-api-component) 19 | - [Installation](#installation) 20 | - [Manual Configuration](#manual-configuration) 21 | - [Sensor](#sensor) 22 | - [Microsoft Teams](#microsoft-teams) 23 | 24 | ### Installation 25 | 26 | - If it doesn't already exist, create a directory called `microsoft_graph` under `config/custom_components/`. 27 | - Copy all files in `custom_components/microsoft_graph` to your `config/custom_components/microsoft_graph/` directory. 28 | - Restart Home Assistant to pick up the new integration. 29 | - Configure with config below. 30 | 31 | ### Manual Configuration 32 | 33 | You will need to register an application in Azure AD and retrieve the client ID and client secret: 34 | 35 | 36 | #### MS Azure account set up 37 | - Register a new application in Azure AD, Click “New Registration” 38 | - URL: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade 39 | - Name your application 40 | - Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant)" under supported account types 41 | - Ignore Redirect URI (for now), click “Register” 42 | - You will now be on the “Overview” page for your app, copy the “Application (client) ID” value and save it for later 43 | - In the left menu, select “Authentication” 44 | - Click “Add a platform” 45 | - Select “Web” 46 | - Add **https://:PORT/auth/external/callback** 47 | - Click the “Configure” button 48 | - Still In the Authentications Tab, select “Add URI” 49 | - Paste in **https://my.home-assistant.io/redirect/oauth** 50 | - Scroll down and ticket “Access tokens” and “ID Tokens” check boxes 51 | - Scroll down further, select the “Accounts in any organizational directory (Any Azure AD directory - Multitenant)” radio button 52 | - Click the “Save” button 53 | - In the left menu, select “Certificates & secrets” 54 | - Click “New client secret” 55 | - Set a description and expiration date (max 24 months), click the “add” button 56 | - You will now be presented a ‘value’ and ‘secret ID’. Copy the **"value"** field and save it for later 57 | - In the left menu, select “API Permissions” 58 | - Click “Add a Permission” 59 | - Select “Microsoft Graph” 60 | - Select “Delegated Permissions” 61 | - Search for Presence.Read, expand “Presence” and select “Presence.Read” 62 | - Search for User.Read, expand “User” and select “User.Read” 63 | - Click the “Add permissions” button 64 | - **You are done with Azure** 65 | 66 | 67 | 68 | #### Home Assistant Setup 69 | - Go to settings > Devices & services > Add Integration > Microsoft Graph 70 | - Input your **ClientID** and **Secret Value** from earlier, click “next” 71 | - A new tab will open with “Link account to Home Assistant?”, Click “Link account” 72 | 73 | 74 | ### Sensor 75 | 76 | The Microsoft Graph sensor platform automatically tracks various resources from your Office 365 tenant. 77 | 78 | #### Microsoft Teams 79 | 80 | There are 2 sensors that are added, both of which are enabled by default. 81 | 82 | | Entity ID | Default | Description | 83 | | ---------------------------------| ------ | -----------------------------------------------------------------------------| 84 | | `sensor.ms_teams_availability` | Enabled | Shows your availability (e.g. Available, AvailableIdle, Away). | 85 | | `sensor.ms_teams_activity` | Enabled | Shows your activity (e.g. InACall, InAConferenceCall, Inactive, InAMeeting). | 86 | 87 | See possible availability and activity values [here](https://docs.microsoft.com/en-us/graph/api/resources/presence?view=graph-rest-beta#properties). 88 | --------------------------------------------------------------------------------