How to be on time every time? Moving around big cities is no easy matter because of the numerous people living around the area. A fixed alarm is often used to let you know about an upcoming event, but it does not provide a reliable departure time. Ideally, we should consider not only the current time and travel duration but also traffic conditions.
This project demonstrates how to build a device capable of solving this issue. The Smart Calendar gets the user’s current location and destination to obtain the travel duration, including traffic time. Then, with this data, it calculates at which time the user should leave to be on time.
1. Project Description- Smart Calendar: The Particle Argon is the main component of the Smart Calendar; it accesses the Particle Cloud through Wi-Fi to subscribe to and publish the Particle webhooks. It also has an MP3 player that informs the user about upcoming Google Calendar events and the current status of the application.
- Particle webhooks: They handle the HTTP requests required to interact with the Google web APIs and services.
- IFTTT webhook: It chains a phrase of the Google Assistant to an event stored in the Particle Cloud. It is a simple trigger event to inform the Smart Calendar that the user is requesting information about upcoming events.
- Google web APIs and services: The Google APIs used were the Calendar API to access user events, Distance Matrix API to calculate travel duration between the current location and the event location, and Geolocation API to obtain the coordinates of the Smart Calendar based on nearby Wi-Fi access points. Also, the OAuth2.0 protocol was implemented to authorize the use of the Google Calendar API.
The Particle Argon interacts with the MP3 player through a serial interface. It uses pin D9 (TX) and D10 (RX), which correspond to the Argon UART1 interface. An additional digital pin is used to check the MP3 player state, busy or free.
3. Google APIs and OAuth2.0The Google Developer Console provides access to the Google APIs and OAuth2.0 protocol. In this platform, you can create projects and enable usage of the Google APIs. Some of the Google APIs charge for usage, and you need to enable billing before using them. To complete the Smart Calendar project, you must enable billing. The good news is that Google provides $300 credit in a 12-month free trial to get started.
3.1 API Usage and Billing
You can find below some information about usage and billing of the APIs used in this project.
Geolocation: https://developers.google.com/maps/documentation/geolocation/usage-and-billing
Distance Matrix: https://developers.google.com/maps/documentation/distance-matrix/usage-and-billing
Calendar API: https://developers.google.com/calendar/pricing
The Google Calendar API has a courtesy limit of 1, 000, 000 queries per day.
3.2 Setting up the Google Developer Console
1. Go to https://console.developers.google.com
2. Create a new project. On the upper-left corner, you can select an existing project or create a new one.
3. If successfully created, your project should appear in the upper-left corner, indicating that it is the project you are currently working with, and there should be a light blue button that says, "Enable APIs and Services." Click on it and enable the following APIs: Calendar API, Distance Matrix API, and Geolocation API. For each API, you should click the "Enable" button that appears in the API description page.
4. Check that all APIs were correctly enabled. They should appear in a table located at the bottom of the APIs and Services dashboard. In this table, you can also monitor the number of requests made per API.
5. Enable billing. If you have not used your free trial yet, you can enable it by clicking on the "Activate" button in the upper-right corner. Optionally, go to the navigation menu located in the upper-left corner and select billing. Then, click on "Link a billing account". Either way will redirect you to the same place. Once you have been redirected, follow the steps indicated and provide the information requested to enable billing.
For help, go to https://support.google.com/googleapi/answer/6158867
3.3 Setting up OAuth2.0
Some Google APIs, such as the Calendar API, access the user's personal information. Because of this, such APIs must use the OAuth2.0 protocol to request user consent. Auth2.0 implementation requires two credentials (client ID and client secret), to obtain these credentials follow the steps below:
1. Go to the Google Developer Console and make sure that your Smart Calendar project is the one currently selected.
2. In the "APIs & Services" section (available from the navigation menu), go to the "Credentials" view and then click on the "OAuth consent screen" tab. There you have to set the application scope and name. The scope is the level of permission you grant to the Google APIs. For this project, we would grant read-only access to the Google Calendar API. Click on "save" when finished.
Scope: https://www.googleapis.com/auth/calendar.readonly (View your calendars).
3. Select the "Credentials" tab and create a new "OAuth client ID." You can do this by clicking on the "Create credentials" button. For the "OAuth client ID" choose the application type "Other" and give it a name. Once it has been created, you should receive the OAuth2.0 client credentials.
For more detailed information about how to set up OAuth2.0, please go to https://support.google.com/googleapi/answer/6158849
3.4 Setting up an API key
API keys are used for general-purpose Google APIs that do not access the user's data. An API key identifies your project and keeps track of how many requests it has made. For this project, the Distance Matrix and Geolocation API require an API key.
To create an API key, go to the “Credentials” tab, click on the “Create credentials” button, and select the “API key” option. If you want, you can change the default name given to the API key, but it is not mandatory.
For more detailed information about how to set up an API key, please go to https://support.google.com/googleapi/answer/6158862
4. Particle WebhooksWebhooks are essentially HTTP callbacks, and these were used to handle the HTTP requests required to interact with the Google APIs. The following links provided me all the information I needed to understand webhooks and how to create them on the Particle Console. If you are new with webhooks, do not skip them!
Link1: https://docs.particle.io/tutorials/device-cloud/webhooks/
Link2: https://github.com/rickkas7/particle-webhooks
Link3: https://docs.particle.io/reference/device-cloud/webhooks/
The Particle webhooks implemented in the Smart Calendar share the following features:
1. Built with a JSON template that uses mustache (go to Link2 for help).
2.Custom response and error response event name that contains the device ID so the device that triggered an event will be the only one to get the response (go to Link3 for help).
Because of this, you must type your device ID whenever you see,
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>"
The webhook response and error response event name are customized with,
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}"
"errorResponseTopic":"{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}"
So, to subscribe to any event, the same custom name has to be used, as in the next example code,
String hook_reponse = System.deviceID() + "/hook-response/" + WEBHOOK_EVENT_NAME;
String hook_error = System.deviceID() + "/hook-error/" + WEBHOOK_EVENT_NAME;
Particle.subscribe(hook_reponse, response_handler, MY_DEVICES);
Particle.subscribe(hook_error, error_handler, MY_DEVICES);
3. The data returned by the webhook is specified in the “responseTemplate” filed using the character ‘~’ as the delimiter to separate the values. For instance,
"responseTemplate": "{{{device_code}}}~{{{user_code}}}~{{{verification_url}}}~{{{expires_in}}}~{{{interval}}}"
4. Data is passed to the webhook as part of the Particle.puslish() function, as in the next example code,
String data = String::format("{\"client_id\":\"%s\"}", CLIENT_ID.c_str());
Particle.publish(WEBHOOK_EVENT_NAME, data, PRIVATE);
Then, the passed data is accessed inside the webhook using triple braces {{{client_id}}} to avoid escaping.
"json": {
"client_id": "{{{client_id}}}",
"scope": "email https://www.googleapis.com/auth/calendar.readonly"
}
4.1 Google OAuth2.0
Source: https://developers.google.com/identity/protocols/OAuth2ForDevices
Because Google APIs target all sorts of applications, the OAuth2.0 protocol provides support for different scenarios. The one implemented in this project is for TV and Limited-Input Device Applications. In this scenario, the OAuth2.0 protocol works as follows:
1. The application has to request a user code to Google Servers. If successful, it will return an URL and authorization code.
2. Then, the user has to copy and paste the URL in a web browser and enter the given user code into the input text that appears on the screen. If the user has multiple Google accounts, it has to select to which account it would grant user consent.
3. Because the application cannot determine when the user has responded to the request (step 2), it has to poll Google’s authorization server continuously. The permitted polling rate is provided in the data returned by the first webhook, and it is usually around 5 seconds.
4. Once the user has responded to the request, Google’s authorization server returns an access token, a refresh token, and the lifetime of the access token, which is usually around 1 hour. The application then uses the access token to call the Google API.
5. Since the application has a limited lifetime, it must refresh the access token. Refreshing the access token does not need the user's intervention anymore, but it requires the refresh token provided in the second webhook.
4.1.1 OAuth2.0 User code
JSON webhook template:
HTTP POST callback triggered by the event name oauth_usr_code
.
{
"event": "oauth_usr_code",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"errorResponseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://accounts.google.com/o/oauth2/device/code",
"requestType": "POST",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{device_code}}}~{{{user_code}}}~{{{verification_url}}}~{{{expires_in}}}~{{{interval}}}",
"json": {
"client_id": "{{{client_id}}}",
"scope": "email https://www.googleapis.com/auth/calendar.readonly"
}
}
client_id
: The OAuth2.0 client ID for your application.scope
: A list of scopes that identify the resources that the application could access on the user's behalf. It should contain the scope previously selected in the OAuth consent screen when setting up OAuth2.0.
Webhook response for successful requests (HTTP 200 OK):
{
"device_code" : "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8",
"user_code" : "GQVQ-JKEC",
"verification_url" : "https://www.google.com/device",
"expires_in" : 1800,
"interval" : 5
}
device_code
: A unique value to identify the device running the application.user_code
: A value to identify the scopes requested by the application.verification_url
: A URL that the user must navigate to grant or deny access to the application.expires_in
: Lifetime of the device code and user code in seconds.interval
: Polling rate permitted by Google's authorization server in seconds.
Common webhook error responses:
- HTTP 401 Unauthorized: This error indicates that your client ID is invalid. A mistype in the client ID could cause this error.
4.1.2 OAuth2.0 Poll authorization
JSON webhook template:
HTTP POST callback triggered by the event name oauth_poll_auth
.
{
"event": "oauth_poll_auth",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"errorResponseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://www.googleapis.com/oauth2/v4/token",
"requestType": "POST",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{access_token}}}~{{{refresh_token}}}~{{{expires_in}}}",
"json": {
"client_id": "{{{client_id}}}",
"client_secret": "{{{client_secret}}}",
"code": "{{{code}}}",
"grant_type": "http://oauth.net/grant_type/device/1.0"
}
}
client_id
: The OAuth2.0 client ID for your application.client_secret
: The OAuth2.0 client secret for your application.code
: The device code that the authorization server returned.grant_type
: Fixed value defined by Google.
Webhook response for successful requests (HTTP 200 OK):
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
access_token
: A token that the application sends to authorize Google Calendar API requests.expires_in
: Lifetime of the access token in seconds.toke_type
: The type of token returned.refresh_token
: A token that the application uses to obtain a new access token.
Common webhook error responses:
- HTTP 428 Precondition Required: For the application, this is technically not an error since it is indicating that user authorization is pending. In other words, the user has not completed step 2 of the OAuth2.0 protocol. However, the Particle Cloud treats all HTTP status codes different than 200 as errors.
- HTTP 403 Forbidden: This error indicates that the user has denied access to the application.
- HTTP 401 Unauthorized: This error indicates that your client secret is invalid. A mistype in the client secret could cause this error.
4.1.3 OAuth2.0 Refresh token
JSON webhook template:
HTTP POST callback triggered by the event name oauth_ref_token
.
{
"event": "oauth_ref_token",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"errorResponseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://www.googleapis.com/oauth2/v4/token",
"requestType": "POST",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{access_token}}}~{{{expires_in}}}",
"json": {
"refresh_token": "{{{refresh_token}}}",
"client_id": "{{{client_id}}}",
"client_secret": "{{{client_secret}}}",
"grant_type": "refresh_token"
}
}
refresh_token
: The refresh token returned from the authorization code exchange.client_id
: The OAuth2.0 client ID for your application.client_secret
: The OAuth2.0 client secret for your application.grant_type
: Fixed value defined defined by Google.
Webhook response for successful requests (HTTP 200 OK):
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3600,
"token_type":"Bearer"
}
4.2 Google Calendar API
Source: https://developers.google.com/calendar/v3/reference/events/list
JSON webhook template:
HTTP GET callback triggered by the event name calendar_event
.
{
"event": "calendar_event",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"errorResponseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://www.googleapis.com/calendar/v3/calendars/{{{calendar_id}}}/events?",
"requestType": "GET",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{items.0.start.dateTime}}}~{{{items.0.location}}}",
"headers": {
"Authorization": "Bearer {{{access_token}}}"
},
"query": {
"orderBy": "starttime",
"singleEvents": true,
"maxResults": 1,
"timeMin": "{{{time_min}}}",
"timeMax": "{{{time_max}}}"
}
}
calendar_id
: Calendar identifier.orderBy
: Order by the start date/time (ascending).singleEvents
: It must be true to useorderBy
.maxResults
: Maximum number of events returned.time_min
: Lower bound for an event's end time to filter by. In the application, this value set to the current time.time_max
: Upper bound for an event's start time to filter by. In the application, this value is set to the current time plus three hours, but can be changed.
In addition, as indicated in OAuth2.0 for TV and Limited-Input Device Applications, an Authorization: Bearer
HTTP header is used to pass the access_token
.
Webhook response for successful requests (HTTP 200 OK), this is just an example highlighting the important data:
{
"kind": "calendar#events",
"etag": etag,
"summary": string,
"description": string,
"updated": datetime,
"timeZone": string,
"accessRole": string,
"defaultReminders": [ ... ],
"items": [
{
"location": "PLAYHOUSE Whitley Bay, Marine Ave, Whitley Bay NE26 1LZ, UK",
"start": {
"dateTime": "2019-06-28T17:00:00+01:00",
"timeZone": "Europe/London"
}
}
]
}
From the list of events on the calendar, which are stored in items
, only one event is returned. If there are several events scheduled between time_min
and time_max
, then the closest to time_min
is returned. The values considered from the event object are location
and start dateTime
.
Common webhook error responses:
- HTTP 404 Bad Request: If the requested ordering for the HTTP GET query is invalid, this error will occur. It should not happen unless the HTTP GET query is modified with an invalid ordering or invalid parameters.
- HTTP 401 Unauthorized: If the application stops refreshing the access token, this error will occur. It should not happen unless the application algorithm is modified in such a way that blocks refreshing.
- HTTP 404 Not Found: This error indicates that your calendar ID was not found. A mistype in the calendar ID could cause this error.
4.3 Google Geolocation API
Source: https://developers.google.com/maps/documentation/geolocation/intro
JSON webhook template:
HTTP POST callback triggered by the event name geolocation
.
{
"event": "geolocation",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"errorResponseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-error/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://www.googleapis.com/geolocation/v1/geolocate?",
"requestType": "POST",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{location.lat}}}~{{{location.lng}}}~{{{accuracy}}}",
"headers": {
"Content-Type": "application/json"
},
"query": {
"key": "<TYPE_YOUR_API_KEY_HERE>"
},
"body": "{\"considerIp\":false,\"wifiAccessPoints\":[{{#a}}{\"macAddress\":\"{{m}}\",\"signalStrength\":{{s}},\"channel\":{{c}} },{{/a}}{\"macAddress\":\"{{a.0.m}}\",\"signalStrength\":{{a.0.s}},\"channel\":{{a.0.c}} }]}"
}
considerIP
: A flag to enable IP geolocation if Wi-Fi signals are not available.wifiAccessPoints
: An array of Wi-Fi access point objects. Each object contains the MAC address of the Wi-Fi node, the current signal strength measured in dBm, and the channel over which the client is communicating with the access point.
By using mustache, it was possible access and define a variable number of Wi-Fi access points in the body
of the POST request. If you want to understand better how mustache works, please go to Link2 for help.
Also, as indicated in the Developer Guide of this API, the API key
is passed in the request header using the query filed.
Webhook response for successful requests (HTTP 200 OK):
{
"location": {
"lat": 51.0,
"lng": -0.1
},
"accuracy": 1200.4
}
location
: The user’s estimated latitude and longitude, in degrees.accuracy
: The accuracy of the estimated location, in meters.
Common webhook error responses:
- HTTP 404 Bad Request: This error indicates that either your API key or request body is invalid. A mistype in the API key could cause this error.
- HTTP 403 Forbidden: If you applied restrictions to the API key, this error could occur.
- HTTP 404 Not Found: This error indicates that the request was valid, but the API was unable to locate your device. It could happen if there are not enough Wi-Fi access point within the range area.
4.4 Google Distance Matrix API
Source: https://developers.google.com/maps/documentation/distance-matrix/intro
JSON webhook template:
HTTP GET callbacks triggered by the event names dist_driving
and dist_transit
. Based on the Developer Guide, only driving supports traffic information.
Driving (By car)
{
"event": "dist_driving",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://maps.googleapis.com/maps/api/distancematrix/json?",
"requestType": "GET",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{rows.0.elements.0.distance.text}}}~{{{rows.0.elements.0.duration_in_traffic.value}}}~{{{rows.0.elements.0.status}}}~{{{status}}}",
"query": {
"origins": "{{{origin}}}",
"destinations": "{{{destination}}}",
"mode": "driving",
"units": "imperial",
"departure_time": "{{{curr_time}}}",
"key": "<TYPE_YOUR_API_KEY_HERE>"
}
}
Transit (Public Transport)
{
"event": "dist_transit",
"deviceID": "<TYPE_YOUR_DEVICE_ID_HERE>",
"responseTopic": "{{{PARTICLE_DEVICE_ID}}}/hook-response/{{{PARTICLE_EVENT_NAME}}}",
"url": "https://maps.googleapis.com/maps/api/distancematrix/json?",
"requestType": "GET",
"noDefaults": true,
"rejectUnauthorized": true,
"responseTemplate": "{{{rows.0.elements.0.distance.text}}}~{{{rows.0.elements.0.duration.value}}}~{{{rows.0.elements.0.status}}}~{{{status}}}",
"query": {
"origins": "{{{origin}}}",
"destinations": "{{{destination}}}",
"mode": "transit",
"units": "imperial",
"transit_mode": "{{{transit_mode}}}",
"transit_routing_preference": "less_walking",
"key": "<TYPE_YOUR_API_KEY_HERE>"
}
}
origins
: The starting point for calculating travel distance and time. It can be supply as an address or latitude/longitude coordinates.destinations
: The finishing point for calculating travel distance and time. It can be supply as an address or latitude/longitude coordinates.mode
: The mode of transport to use when calculating distance.units
: The unit system to use when expressing distance as text.departure_time
: The desired time of departure. By adding this filed, traffic conditions are taken into account. In the application, this value is set to the current time.transit_mode
: Specifies the preferred mode of transit.transit_routing_preference
: Specifies preferences for transit requests. For this application, it was indicated that the calculated route should prefer limited amounts of walking.key
: Your application's API key.
Webhook response for successful requests (HTTP 200 OK):
{
"destination_addresses" : [ ... ],
"origin_addresses" : [ ... ],
"rows" : [ {
"elements" : [ {
"distance" : { "text" : "11.0 mi", "value" : 17683},
"duration" : { "text" : "28 mins", "value" : 1651},
"duration_in_traffic" : { "text" : "33 mins", "value" : 1986},
"status" : "OK"
} ]
} ],
"status" : "OK"
}
distance
: The total route distance. Expressed in meters (value) and as text (imperial/metric depends on the units set).duration
: The total time it takes to travel the given route without traffic conditions. Expressed in seconds (value) and as text (hours-mins).duration_in_traffic
: The total time it takes to travel the given route, based on current and historical traffic conditions. Expressed in seconds (value) and as text (hours-mins). This filed is only available fordriving
.row.0.elements.0.status
: Element-level status code. A status field with information about that particular origin-destination pairing.status
: Top-level status codes. A status fields within the response object contain the status of the request.
4.5 Creating webhooks in the Particle Console
It is import that you claim your Particle Argon before using the webhooks. To set up your device, go to https://docs.particle.io/quickstart/argon/
Once you have claimed your device, follow the steps below to create a webhook in the Particle Console.
1. Go to https://console.particle.io/ and create an account.
2. Click on the "Integrations" icon located in the bar at the left side of the screen. Then, in the integrations view, click on "New Integration."
3. Choose the Webhook option.
4. In the webhook view, click on the "Custom Template" tab and paste one of the JSON webhook templates. Then, click on create.
The last webhook needed for the application is the one that chains a phrase of the Google Assistant to an event stored in the Particle Cloud. To create this webhook, follow the steps below:
1. Go to https://ifttt.com and create an account.
2. Go to "My Applets" and create a new applet.
3. For "this +, " choose as service Google Assistant and "Say a simple phrase" as the trigger. If you have never used this service, you would have to grant access. Then, write the phrases you want to use to interact with the Smart Calendar. I like things simple, so I choose "event status" as my phrase.
4. For "+ that, " choose as service Particle and "Publish an event" as the action. If you have never used this service, you would have to grant access. Then, for the event name write "google_assistant" and make it a private event.
All mp3 files used in the application are provided on the GitHub. To set up the mp3 player, you will need a micro SD card. The DFPlayer Mini supports TF cards with FAT16, FAT32 file system. Before copying the mp3 files into the micro SD card, I would recommend to format it to avoid any issues.
Folders and mp3 files are named according to the specifications of the manufacturer. The mp3 player can handle a maximum of 100 folders, from 01-99, and a maximum of 256 files per folder, from 001-255. Please do not change the names or it will not work.
6.1 Personalized MP3 files
If you do not like the existing files or you would like to personalize the application, then you can do it with a text-to-speech converter. I used the Convert Text to Speech app available in the Microsoft store, but there are also several text-to-speech online converters that you can use for free. Using those converters is quite straightforward, you only have to write a text, and then a link for downloading is provided.
7. Google CalendarTo avoid problems with the Smart Calendar, check that the Google Calendar is using the appropriate time zone. Open the app, click on the navigation menu, go to Setting and then General, there you should see the time zone used by the app.
7.1 Calendar IDs
Google Calendar has one primary calendar named "Events" whose ID is your gmail address. This calendar comes by default with the app, and you cannot delete it. There are also secondary calendars, which are the ones that you create. In my Google Calendar, I have a secondary calendar named "Party."
If you want to use a secondary calendar instead of the primary, follow the steps below to obtain the calendar IDs.
1. Go to https://developers.google.com/oauthplayground/
2. Search on the API list the Calendar API v3 and choose the scope.../auth/calendar.readonly. Then, click on the "Authorize APIs" button. To continue, you would have to grant access to Google OAuth2.0 Playground.
3. Click on the "Exchange authorization code for tokens" button.
4. Enter the following request URI in the input filed,
URI: https://www.googleapis.com/calendar/v3/users/me/calendarList
5. In the response body of the HTTP response, find the array of calendar objects named items
. There you should see an object for each calendar in your Google account. Within the object, the key summary
contains the name of the calendar and id
the calendar identifier.
The following illustrations show the program flow with respect to the application stages (failures are omitted for simplicity), and the OAuth2.0 stage during initialization.
GEOLOCATION
: It runs only during the initialization. At this stage, the device location is requested via the Geolocation webhook.OAUTH2
: The first time the application runs, the user must authenticate it, or grant user consent. This is taking care of at the initial OAuth2.0 stage. To avoid repeated user intervention, the OAuth2.0 refresh token is stored in memory, so in the next boot, the access token is refreshed without the user being aware of it. If the access token expires during regular operation, the program switches to OAuth2 to refresh it.ASSISTANT
: After initialization, the program enters into Assistant mode awaiting for a user request. It is at this stage when the user can interact with the Smart Calendar through the Google Assistant.CALENDAR
: If the user has made a request and the access token is still valid, then the program would switch to Calendar mode to publish the Calendar webhook and get the event location and start time.DISTANCE_MATRIX
: With the information obtained from both the Geolocation and Calendar API, at this stage, the application publishes the Distance Matrix webhook to get an estimated travel time to the event location.DATA_PROCESSING
: At this final stage, the program takes the travel duration and provides an ideal departure time based on the current time and event start time.
To build your Smart Calendar application, first, go to GitHub and clone the project repository. Once you get the source code, follow the steps below to create and run the Smart Calendar application on your device.
1. Go to https://build.particle.io/build
2. Create a new project. Give it a name and SAVE it before moving to the next step.
3. Add the.cpp and.h files. Click on the add button in the upper-right corner to add a pair of files. Rename each file according to the names in the project repository and paste the source code. For.h only files, delete the.cpp by clicking on the "x."
4. Save the project and compile it to make sure that there are no errors. Before flashing the code, set up the app.h file as indicated in the next subsection; also check that your device is selected in the lower-right corner.
9.1 Setting up the application file
Open the app.h file and modify the following lines according to your application.
1. Using the Geolocation API is optional. If it fails to return the location of your device (HTTP 404 Not Found), comment out GEOLOC_ENABLED
.
#define GEOLOC_ENABLED
2. Define a limit, in meters, for the accuracy returned by the Geolocation API. If the value returned from the API is greater than GEOLOC_MINIMUM_ACC
, an error will occur.
#define GEOLOC_MINIMUM_ACC 50 // in meters
3. Set your TIME_ZONE
. You must consider Daylight Saving Time (DST) changes since the program does not take care of it.
const int8_t TIME_ZONE = +0;
4. Set your OAuth2.0 credentials CLIENT_ID
and CLIENT_SECRET
.
const String CLIENT_ID = "<TYPE_YOUR_CLIENT_ID_HERE>";
const String CLIENT_SECRET = "<TYPE_YOUR_CLIENT_SECRET_HERE>";
5. Choose a CALENDAR_ID
. It could be your gmail address (primary calendar, Events), or any other secondary calendar identifier.
const String CALENDAR_ID = "<TYPE_YOUR_CALENDAR_ID_HERE>";
6. In the setup()
function, set your preferred travel mode, either driving or transit. If transit, then choose a transit mode. IMPORTANT: Only driving considers traffic duration when calculating the ideal departure time.
// Configure the distance matrix event.
// Travel Mode: DRIVING, TRANSIT.
Distance_Matrix_Event.travel_mode = Distance_Matrix_Travel_Mode::DRIVING;
// Transit Mode: SUBWAY, TRAIN, TRAM, RAIL, NONE.
Distance_Matrix_Event.transit_mode = Distance_Matrix_Transit_Mode::NONE;
7. If Geolocation was disabled, then in the setup()
function, you must type the location of your device.
// Set the device location
Distance_Matrix_Event.origin_lat = 0.0;
Distance_Matrix_Event.origin_lng = 0.0;
9.2 Serial terminal(Putty)
A serial terminal is required for the first boot to get the URL and user code that would help you to authenticate your device. For this, I recommend using putty.
Download putty: https://www.putty.org/
Download the executable (.exe), open it, and configure it as follows:
- In Session, select connection type "Serial" and set the speed to 9600 and write the COM assigned to your Particle Argon.
- In Terminal, for "Local line editing" select select "Force on." This is required to enter data into the serial terminal. During the first boot, you must press enter to continue.
10.1 Granting user consent
For the first run, you must authenticate your Smart Calendar using Google OAuth2.0 so that it can use the Calendar API. If successful, it stores the refresh token in memory.
10.2 Refreshing access token.
If authenticated, the Smart Calendar uses the refresh token stored in memory to refresh the access token whenever it expires or when the Particle Argon is powered up. IMPORTANT: If an error occurs at this stage, the refresh token will be erased from memory, and you will have to grant user consent again.
10.3 User request - Driving
Input data
- Device location: Grainger Hotel, Westgate Rd, Newcastle upon Tyne NE4 6UJ (Grainger Hotel).
- Request time: 6:14 pm.
- Event location: Marine Ave, Whitley Bay NE26 1LZ (PLAYHOUSE Whitley Bay).
- Event start time: 7 pm.
Output data
- Travel duration: 31 min, or 1863 sec (returned by Distance Matrix API).
- Estimated departure time: 6:28 pm.
The route selected by the Distance Matrix API is the fastest based on historical traffic conditions and live traffic.
10.4 User request - Transit (Bus)
Input data
- Device location: Grainger Hotel, Westgate Rd, Newcastle upon Tyne NE4 6UJ (Grainger Hotel).
- Request time: 8:19 pm.
- Event location: Marine Ave, Whitley Bay NE26 1LZ (PLAYHOUSE Whitley Bay).
- Event start time: 9 pm.
Output data
- Travel duration: 51 min, or 3063 sec (returned by Distance Matrix API).
- Already late for the upcoming event by 10 min.
The route selected by the Distance Matrix API is the one with the least amount of walking minutes. So the bus stop picked by the API is likely to be the closest to your current location. One negative aspect of using transit is that the timetable of the choose transit mode is not considered and therefore, the estimated departure time could be wrong. Because of this, the user must first consider the timetables before relying on the returned departure time.
Comments