Rebecca Deprey
Published

Make a Meeting Indicator Screen with a Raspberry Pi

Let others in your home know when you're in a meeting

BeginnerFull instructions provided14,194
Make a Meeting Indicator Screen with a Raspberry Pi

Things used in this project

Hardware components

Raspberry Pi Zero
Raspberry Pi Zero
×1
Adafruit i2c RGB Positive 16x2 LCD screen
×1

Software apps and online services

Microsoft Azure
Microsoft Azure
Pushover

Story

Read more

Code

Code snippet #5

Plain text
// A date object representing the current date and time in UTC
const dateTime = DateTime.utc()

// A date string representing the work start time (9 am) for the current date in UTC
const workStart = DateTime.utc(
    dateTime.year,
    dateTime.month,
    dateTime.day,
    14,
    0,
    0
).toString()

// A date string representing the work end time (5 pm) for the current date in UTC
const workEnd = DateTime.utc(
    dateTime.year,
    dateTime.month,
    dateTime.day,
    22,
    0,
    0
).toString()

Code snippet #6

Plain text
// A date object representing the current date and time in UTC
const dateTime = DateTime.utc()

// A date string representing the work start time (9 am) for the current date in UTC
const workStart = DateTime.utc(
    dateTime.year,
    dateTime.month,
    dateTime.day,
    14,
    0,
    0
).toString()

// A date string representing the work end time (5 pm) for the current date in UTC
const workEnd = DateTime.utc(
    dateTime.year,
    dateTime.month,
    dateTime.day,
    22,
    0,
    0
).toString()

Code snippet #7

Plain text
// Numeric value representing day of week; 6 is Saturday and 7 is Sunday
const { weekday } = dateTime

if (weekday === 6 || weekday === 7) {
    turnOffScreen()
    return
}

Code snippet #8

Plain text
// Numeric value representing day of week; 6 is Saturday and 7 is Sunday
const { weekday } = dateTime

if (weekday === 6 || weekday === 7) {
    turnOffScreen()
    return
}

Code snippet #11

Plain text
const start = async () => {
  try {
    const currentDateTime = DateTime.utc().toString()

    // Turn off the screen outside of work hours
    if (
      Date.parse(workStart) > Date.parse(currentDateTime) ||
      Date.parse(currentDateTime) > Date.parse(workEnd)
    ) {
      turnOffScreen()
      return
    }

    const meetings = await fetchMeetings()

    if (!meetings || meetings.length === 0) {
      turnOffScreen()
      return
    }

    const isHappeningNow = isMeetingHappeningNow(meetings)

    // Clear the previous message from the screen
    lcd.clear()

    // If there's a meeting happening, show a message and make the screen red
    if (isHappeningNow) {
      lcd.backlight(lcd.colors.RED)
      lcd.message('Meeting in\nProgress!')
    } else {
      // If there isn't a meeting happening, show a message and make the screen green
      lcd.backlight(lcd.colors.GREEN)
      lcd.message("I'm free!")
    }
  } catch (error) {
    console.log(error)
    await fetch(
      `https://api.pushover.net/1/messages.json?title=Meeting+Indicator+Error&message=${error.message}&token=${process.env.PUSHOVER_TOKEN}&user=${process.env.PUSHOVER_USER}`,
      { method: 'POST' }
    )
  }
}

cron.schedule('*/15 * * * *', start) // runs once every 15 minutes
start()

Code snippet #12

Plain text
const start = async () => {
  try {
    const currentDateTime = DateTime.utc().toString()

    // Turn off the screen outside of work hours
    if (
      Date.parse(workStart) > Date.parse(currentDateTime) ||
      Date.parse(currentDateTime) > Date.parse(workEnd)
    ) {
      turnOffScreen()
      return
    }

    const meetings = await fetchMeetings()

    if (!meetings || meetings.length === 0) {
      turnOffScreen()
      return
    }

    const isHappeningNow = isMeetingHappeningNow(meetings)

    // Clear the previous message from the screen
    lcd.clear()

    // If there's a meeting happening, show a message and make the screen red
    if (isHappeningNow) {
      lcd.backlight(lcd.colors.RED)
      lcd.message('Meeting in\nProgress!')
    } else {
      // If there isn't a meeting happening, show a message and make the screen green
      lcd.backlight(lcd.colors.GREEN)
      lcd.message("I'm free!")
    }
  } catch (error) {
    console.log(error)
    await fetch(
      `https://api.pushover.net/1/messages.json?title=Meeting+Indicator+Error&message=${error.message}&token=${process.env.PUSHOVER_TOKEN}&user=${process.env.PUSHOVER_USER}`,
      { method: 'POST' }
    )
  }
}

cron.schedule('*/15 * * * *', start) // runs once every 15 minutes
start()

Code snippet #13

Plain text
// Gets the Microsoft Graph Explorer API auth token
const getAuthToken = async () => {
    const authResponse = await getToken(tokenRequest)
    return authResponse.accessToken
}

Code snippet #14

Plain text
// Gets the Microsoft Graph Explorer API auth token
const getAuthToken = async () => {
    const authResponse = await getToken(tokenRequest)
    return authResponse.accessToken
}

Code snippet #17

Plain text
// Get's my user ID from Microsoft Graph Explorer API
const getUserId = async (apiOptions) => {
    const userRes = await fetch(apiConfig.uri, apiOptions)
    const { value } = await userRes.json()
    return value[0].id
}

Code snippet #18

Plain text
// Get's my user ID from Microsoft Graph Explorer API
const getUserId = async (apiOptions) => {
    const userRes = await fetch(apiConfig.uri, apiOptions)
    const { value } = await userRes.json()
    return value[0].id
}

Code snippet #19

Plain text
// Request:
// https://graph.microsoft.com/v1.0/users/USER_ID/calendars/CALENDAR_ID/calendarview?startdatetime=2022-01-14T14:00:00.0000000&enddatetime=2022-01-14T15:30:00.0000000

// Sample Response:
{
"@odata.context": "<https://graph.microsoft.com/v1.0/$metadata#users('USER_ID')/calendars('CALENDAR_ID')/calendarView>",
"value": [
    {
        "@odata.etag": "",
        "id": "EVENT_ID",
        "createdDateTime": "2022-01-04T01:46:27.7022336Z",
        "lastModifiedDateTime": "2022-01-10T18:15:46.6799499Z",
        "changeKey": "",
        "categories": [],
        "transactionId": null,
        "originalStartTimeZone": "Eastern Standard Time",
        "originalEndTimeZone": "Eastern Standard Time",
        "iCalUId": "",
        "reminderMinutesBeforeStart": 15,
        "isReminderOn": false,
        "hasAttachments": false,
        "subject": "Busy",
        "bodyPreview": "",
        "importance": "normal",
        "sensitivity": "normal",
        "isAllDay": false,
        "isCancelled": false,
        "isOrganizer": true,
        "responseRequested": true,
        "seriesMasterId": null,
        "showAs": "busy",
        "type": "singleInstance",
        "webLink": LINK TO OUTLOOK EVENT,
        "onlineMeetingUrl": null,
        "isOnlineMeeting": false,
        "onlineMeetingProvider": "unknown",
        "allowNewTimeProposals": true,
        "isDraft": false,
        "hideAttendees": false,
        "recurrence": null,
        "onlineMeeting": null,
        "responseStatus": {
        "response": "organizer",
        "time": "0001-01-01T00:00:00Z"
    },
    "body": {
        "contentType": "html",
        "content": "<html><head><meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=utf-8\\"><meta name=\\"Generator\\" content=\\"Microsoft Exchange Server\\"><!-- converted from rtf --><style><!-- .EmailQuote { margin-left: 1pt; padding-left: 4pt; border-left: #800000 2px solid; } --></style></head><body><font face=\\"Times New Roman\\" size=\\"3\\"><span style=\\"font-size:12pt;\\"><a name=\\"BM_BEGIN\\"></a></span></font></body></html>"
    },
    "start": {
        "dateTime": "2022-01-14T14:00:00.0000000",
        "timeZone": "UTC"
    },
    "end": {
        "dateTime": "2022-01-14T17:00:00.0000000",
        "timeZone": "UTC"
    },
    "location": {
        "displayName": "",
        "locationType": "default",
        "uniqueIdType": "unknown",
        "address": {},
        "coordinates": {}
    },
    "locations": [],
    "attendees": [],
    "organizer": {
    "emailAddress": {
        "name": YOUR_NAME,
        "address": EMAIL_ADDRESS
    }}}
    ]
}

Code snippet #20

Plain text
// Request:
// https://graph.microsoft.com/v1.0/users/USER_ID/calendars/CALENDAR_ID/calendarview?startdatetime=2022-01-14T14:00:00.0000000&enddatetime=2022-01-14T15:30:00.0000000

// Sample Response:
{
"@odata.context": "<https://graph.microsoft.com/v1.0/$metadata#users('USER_ID')/calendars('CALENDAR_ID')/calendarView>",
"value": [
    {
        "@odata.etag": "",
        "id": "EVENT_ID",
        "createdDateTime": "2022-01-04T01:46:27.7022336Z",
        "lastModifiedDateTime": "2022-01-10T18:15:46.6799499Z",
        "changeKey": "",
        "categories": [],
        "transactionId": null,
        "originalStartTimeZone": "Eastern Standard Time",
        "originalEndTimeZone": "Eastern Standard Time",
        "iCalUId": "",
        "reminderMinutesBeforeStart": 15,
        "isReminderOn": false,
        "hasAttachments": false,
        "subject": "Busy",
        "bodyPreview": "",
        "importance": "normal",
        "sensitivity": "normal",
        "isAllDay": false,
        "isCancelled": false,
        "isOrganizer": true,
        "responseRequested": true,
        "seriesMasterId": null,
        "showAs": "busy",
        "type": "singleInstance",
        "webLink": LINK TO OUTLOOK EVENT,
        "onlineMeetingUrl": null,
        "isOnlineMeeting": false,
        "onlineMeetingProvider": "unknown",
        "allowNewTimeProposals": true,
        "isDraft": false,
        "hideAttendees": false,
        "recurrence": null,
        "onlineMeeting": null,
        "responseStatus": {
        "response": "organizer",
        "time": "0001-01-01T00:00:00Z"
    },
    "body": {
        "contentType": "html",
        "content": "<html><head><meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=utf-8\\"><meta name=\\"Generator\\" content=\\"Microsoft Exchange Server\\"><!-- converted from rtf --><style><!-- .EmailQuote { margin-left: 1pt; padding-left: 4pt; border-left: #800000 2px solid; } --></style></head><body><font face=\\"Times New Roman\\" size=\\"3\\"><span style=\\"font-size:12pt;\\"><a name=\\"BM_BEGIN\\"></a></span></font></body></html>"
    },
    "start": {
        "dateTime": "2022-01-14T14:00:00.0000000",
        "timeZone": "UTC"
    },
    "end": {
        "dateTime": "2022-01-14T17:00:00.0000000",
        "timeZone": "UTC"
    },
    "location": {
        "displayName": "",
        "locationType": "default",
        "uniqueIdType": "unknown",
        "address": {},
        "coordinates": {}
    },
    "locations": [],
    "attendees": [],
    "organizer": {
    "emailAddress": {
        "name": YOUR_NAME,
        "address": EMAIL_ADDRESS
    }}}
    ]
}

Code snippet #21

Plain text
// Gets the calendar events from Outlook using the Microsoft Graph Explorer API
const fetchMeetings = async () => {
  const accessToken = await getAuthToken()
  const currentDateTime = DateTime.utc().toString()
  const thirtyMinutesFromNow = DateTime.utc().plus({ minutes: 30 }).toString()

  const options = {
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  }

  const userId = await getUserId(options)

  // The OUTLOOK_CALENDAR_ID specifies which of your calendars to get meetings from
  const endpointWithQueryParams = `https://graph.microsoft.com/v1.0/users/${userId}/calendars/${process.env.OUTLOOK_CALENDAR_ID}/calendarview?startdatetime=${currentDateTime}&enddatetime=${thirtyMinutesFromNow}`

  const meetingRes = await fetch(endpointWithQueryParams, options)
  const { value: meetings } = await meetingRes.json()

  return meetings
}

Code snippet #22

Plain text
// Gets the calendar events from Outlook using the Microsoft Graph Explorer API
const fetchMeetings = async () => {
  const accessToken = await getAuthToken()
  const currentDateTime = DateTime.utc().toString()
  const thirtyMinutesFromNow = DateTime.utc().plus({ minutes: 30 }).toString()

  const options = {
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  }

  const userId = await getUserId(options)

  // The OUTLOOK_CALENDAR_ID specifies which of your calendars to get meetings from
  const endpointWithQueryParams = `https://graph.microsoft.com/v1.0/users/${userId}/calendars/${process.env.OUTLOOK_CALENDAR_ID}/calendarview?startdatetime=${currentDateTime}&enddatetime=${thirtyMinutesFromNow}`

  const meetingRes = await fetch(endpointWithQueryParams, options)
  const { value: meetings } = await meetingRes.json()

  return meetings
}

Github

https://github.com/rdeprey/meeting-indicator

Github

https://github.com/Azure-Samples/ms-identity-javascript-nodejs-console

Credits

Rebecca Deprey

Rebecca Deprey

2 projects • 6 followers
Software engineer and IoT/hardware tinkerer. Lover of all things tech, photography, travel, and books. @RadiantSunrise on Twitter.

Comments