Connecting a Seeed Xiao with a Waveshare 1.02 inch e-ink display

After completing the Teams status light, for my next project, I’m moving on to something useful for the other half. This project requires very low power draw while driving an external display. E-ink displays are great for this task and once displayed, the device can go to sleep while still displaying the summary data with no power. As the e-ink display will be using SPI, and my sensors will be using I2C to keep the number of pins down. In the small microprocessors, not all can supply both I2C and SPI, however the Seeeduino Xiao met all my needs. I came across a few challenges on this project, so this post covers how to connect a Seeeduino Xiao with a Waveshare 1.02 e-ink display.


For this project, I used the following parts:

Both Seeed Studio and Waveshare provide guides on how to install the Arduino IDE and get it working with their hardware. This guide covers specifically the wiring and changes made to the code to get the 1.02″ display working with the Seeeduino Xiao

Seeeduino Xiao Pinout selection

The Seeeduino Xiao has 16 pins as below:

For this project, I am using:

3V3, GNDPowering the display and sensors
D8, D10Display SPI pins
D4, D5I2C sensors
D0-D3Display pins

Waveshare 1.02″ e-ink pins

The Waveshare display has the following pins:

Connecting the Seeeduino Xiao with a Waveshare 1.02 inch display

The pins are connected as follows:

Waveshare e-ink displaySeeeduino Xiao

Configuring the example code

Waveshare have example code for their screens running with various hardware platforms on their GitHub page. I downloaded the “epd1in02d” directory and opened it up in the Arduino IDE.

Before compiling the sample code, I first needed to update it to reflect the Seeeduino Xiao pin numbers.

The GPIO pins are defined in DEV_Config.h. I updated the pin numbers to reflect the Xiao’s configuration:

 * GPIO config
#define EPD_RST_PIN         2
#define EPD_DC_PIN          1
#define EPD_CS_PIN          0
#define EPD_BUSY_PIN        3

My test compile still failed, due to the following error:

invalid conversion from 'const unsigned char*' to 'uint8_t* {aka unsigned char*}' [-fpermissive]

This occurred in a couple of places. If I updated the IDE to compile for an Arduino UNO, there were no problems with the compile. This error was specific to the Xiao code compile.

Investigating the errors, I found them to occur with the following files:

  • font24cn.cpp
  • imagedata.cpp

As I’m not using chinese fonts or loading images, I commented out the fonts from the file fonts.h:

// extern cFONT Font12CN;
// extern cFONT Font24CN;

and I commented out the the image processing code from the sample file:

#include "GUI_Paint.h"
#include "DEV_Config.h"
#include "EPD_1in02d.h"
#include "fonts.h"

//#include "imagedata.h"

and removed the image display:

//  EPD_Display_Image(IMAGE_DATA);
//  DEV_Delay_ms(500);
//  EPD_Clear();

At this point, the code successfully compiled and was able to display the first sample page on the display:

Successful sample screen display
Successfully displayed the sample screen

And there you have it, a Seeeduino Xiao with a Waveshare 1.02Now on the rest of the project.

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 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 ''

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 '' -method Get

We can now see the presence information returned:

PS C:\> $presence

@odata.context      :$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:


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        :
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 ''

while ($true)
    $presence = Invoke-RestMethod -Headers @{Authorization = "Bearer $($graphaccesstoken.GetAccessToken())" } -Uri '' -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

  name: light_meetingstatus
  platform: ESP8266
  board: esp01_1m

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

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


# Enable logging

# Enable Home Assistant API
# api:


  port: 80
  - platform: neopixelbus
    type: GRB
    pin: GPIO3
    num_leds: 24
    id: light_meetingstatus
    name: "Meeting Light"
    - addressable_color_wipe:
        name: "Red Wipe"
          - 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.