Building an automated Microsoft Teams status light – Part 2

In “Building an automated Microsoft Teams status light – Part 1”, I outlined the very basic method of how I wired up a NeoPixel LED to an ESP8266 and connected it to HomeAssistant. Any RGB smart bulb connected to HomeAssistant will achieve the same goal, I just wanted something that can sit on the bookshelf that sits outside my makeshift office.

In this part, I will be showing how to connect to your online teams presence to retrieve your current status.

Set up your Azure Active Directory

The first step is to create an AAD application and grant the appropriate rights. In order to achieve this, you will need the “Application Developer” role if “Users can register applications” has been set to “No” in your AAD. I set up my initial registration as follows:

Register an application

Once registered, I then needed to set up the permissions for the application to query presence information. The application requires the following permissions:

  • Presence.Read
  • User.Read

As I was also toying with some calendar information, I added calendar.read rights as well. The last step was to create a secret for the application:

Now that this is all complete, record the following information:

  • Tenant ID
  • Client ID
  • Client secret

These are all required for PowerShell to collect your presence information.

Query AAD for presence information with PowerShell

For this piece of code, I will be using the PSMSGraph module to get the presence information.

# Import PSMSGraph
Import-Module PSMSGraph

Using the information recorded earlier, create the following variables:

# Tenant and Client IDs
$tenantID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$clientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Create Secret
$clientSecret = (ConvertTo-SecureString "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -AsPlainText -Force)

With all of the required ID’s and secrets set, I now call the Graph application and get the access tokens. This will prompt for you to log in with your O365 credentials.

# Call the Graph App
$GraphApp = New-GraphApplication -Name "presence-app" -Tenant $tenantID -ClientCredential $creds -RedirectUri "http://localhost/" 

# This will prompt you to log in with your O365/Azure credentials and authorise access
$AuthCode = $GraphApp | Get-GraphOauthAuthorizationCode
$GraphAccessToken = $AuthCode | Get-GraphOauthAccessToken -Resource 'https://graph.microsoft.com'

Now that I have an access token, I am able to make a REST call to get the presence information:

$presence = Invoke-RestMethod -Headers @{Authorization = "Bearer $($graphaccesstoken.GetAccessToken())" } -Uri 'https://graph.microsoft.com/beta/me/presence' -method Get

We can now see the presence information returned:

PS C:\> $presence


@odata.context      : https://graph.microsoft.com/beta/$metadata#users('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')/presence/$entity
id                  : xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxxxx
availability        : Available
activity            : Available
outOfOfficeSettings : @{message=; isOutOfOffice=False}

We can see two properties on the object:

  • availability
  • activity

The following information is what I’m using. Availability is the primary control I’m using for the light. Activity is allowing me to link an additional light that activates the webcam ring light when I join a video call:

AvailabilityAvailable
Busy
Away
DoNotDisturb
ActivityAvailable
InACall
InAConferenceCall

Now that I have the presence information, I can loop with a delay to get this information, however the access token will eventually expire. If we look at the access token properties we can see the following:

PS C:\> $graphaccesstoken


GUID            : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
RequestedDate   : 8/03/2022 8:04:25 PM
LastRequestDate : 8/03/2022 8:04:25 PM
IsExpired       : False
Expires         : 8/03/2022 9:18:17 PM
ExpiresUTC      : 8/03/2022 10:18:17 AM
NotBefore       : 8/03/2022 7:59:27 PM
NotBeforeUTC    : 8/03/2022 8:59:27 AM
Scope           : {Calendars.Read, Presence.Read, User.Read}
Resource        : https://graph.microsoft.com
IsRefreshable   : True
Application     : Guid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Name: presence-app

From the properties, we can see “IsExpired” and “IsRefreshable”. When the token expires, we can update the token without the need for re-authentication:

    if ($GraphAccessToken.IsExpired)
    {
        $GraphAccessToken | Update-GraphOAuthAccessToken -Verbose    
    }

Putting this all together, my code looks like:

# Import PSMSGraph
Import-Module PSMSGraph

# Tenant and Client IDs
$tenantID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$clientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Create Secret
$clientSecret = (ConvertTo-SecureString "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -AsPlainText -Force)

# Create credentials
$creds = [pscredential]::new($clientID, $clientSecret)

# Call the Graph App
$GraphApp = New-GraphApplication -Name "presence-app" -Tenant $tenantID -ClientCredential $creds -RedirectUri "http://localhost/" 

# This will prompt you to log in with your O365/Azure credentials and authorise access
$AuthCode = $GraphApp | Get-GraphOauthAuthorizationCode
$GraphAccessToken = $AuthCode | Get-GraphOauthAccessToken -Resource 'https://graph.microsoft.com'


while ($true)
{
    $presence = Invoke-RestMethod -Headers @{Authorization = "Bearer $($graphaccesstoken.GetAccessToken())" } -Uri 'https://graph.microsoft.com/beta/me/presence' -method Get
    write-host "Availability -" $presence.availability
    Write-Host "Activity -" $presence.activity
    Write-Host "`n"

    Start-Sleep -Seconds 2
    if ($GraphAccessToken.IsExpired)
    {
        $GraphAccessToken | Update-GraphOAuthAccessToken -Verbose    
    }
}

In the next post, I will outline how to update the teams status light in HomeAssistant.

Building an automated Microsoft Teams status light – Part 1

With the increase in the need for working from home due to the on again /off again lockdowns and the slow return to public places (thanks Covid), one of the main issues that I have faced is interruptions during video meetings from the other denizens in the house. To make things easier, I decided to build a teams status light using PowerShell and MSAL. This will provide the other members of my household a way to know when I was or was not in a meeting. I will cover the build in multiple parts:

  • Part 1 – The build of the teams status light itself (this post)
  • Part 2 – The communication of my status

I have decided to build my own light as I wanted something that could sit outside my workspace and be seen easily allowing people coming into the main living area to know my status. It is a very rough build that is well hidden inside the 3D print. There are plenty of better ways to more effectively wire and connect up the components, but this post does not go over this.. This post will cover:

Parts list

Most of the following parts were lying around in my spare parts bucket:

3D Print of the status light

I wanted something that can sit nicely on a bookshelf and be seen easily. So I opted for a globe design that would be printed in clear filament with a rounded square base to house the electronics:

3d model of teams status light
3d Model

Wiring of the teams status light

The aim was to build this from spare parts and I am not an electrical engineer. Therefore I have wired the device in a quick and ready fashion. There are cleaner and more professional ways of achieving this, but this was a simple hack job to achieve some uninterrupted time during meetings.

Software for the ESP8266

HomeAssistant is a tool I am using for other automations around the home and has a REST API that interfaces with sensors and devices. As the LED ring is based on the 5050 RGB set, the neopixelbus is compatible and easily configured. Therefore I opted to install ESPHome onto the wemos D1 for control of the light status. The configuration is below

esphome:
  name: light_meetingstatus
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: "*****"
  password: "*****"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "LightMeetingstatus"
    password: "*****"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
# api:

ota:

web_server:
  port: 80
  
light:
  - platform: neopixelbus
    type: GRB
    pin: GPIO3
    num_leds: 24
    id: light_meetingstatus
    name: "Meeting Light"
    effects:
    - addressable_color_wipe:
        name: "Red Wipe"
        colors:
          - red: 100%
            green: 0%
            blue: 0%
            num_leds: 1
          - red: 0%
            green: 0%
            blue: 0%
            num_leds: 1
        add_led_interval: 100ms
        reverse: False
    - addressable_scan:
        name: "Scan"
        move_interval: 50ms
        scan_width: 3

Once wired up and running, the device appeared within HomeAssistant and was able to be controlled as expected. The real magic sauce comes from the interface with Microsoft Teams and the connection to HomeAssistant, which will be covered in my upcoming posts.