last hackathon i did setup a mqtt integration towards wgtwo's apis to enable call notifications via my ikea trådfri bulb so i can finally notice my wife calling me. the bulb changes color when my phone is ringing and when I am in a call, without me needing to install anything to my phone.
There are a variety of events generated by wgtwo’s systems for subscriptions on the platform. This includes information such as call events, SMS sent to and from a subscription (including content), voicemail events, location events, and more.
We offer a gRPC API which enables developers to listen to a selection of these events.
I wanted to make a super simple setup so that I could configure home automation rules, e.g. "If someone calls me, make my IKEA bulb change color to notify me" or "If I send myself a SMS with 'Leaf ON', turn on the heater in my car".
A lot of home automation tools, such as Node-RED and Home Assistant have great MQTT support, so instead of writing a native gRPC integration I wanted to make a more flexible solution by offering these events over MQTT. This way, anyone using a home automation tool with MQTT support can integrate with our platform easily.
During our last hackathon I built a simple bridge between our events API and MQTT. I wrote this bridge in Go, using Mochi MQTT as an embedded MQTT server.
The flow is shown in this sketch:
The way it works is quite simple: You log in to the service using our OAuth solution. You then grant the service access to fetch events on your behalf. The service will then generate a username and password for you.
The service will fetch events for all subscribers that have enabled it and publish these to the MQTT server with topic {phone number}/events/{type}
. With the generated credentials, you may then subscribe to these events.
Note that nothing of this requires any setup on your phone, so it would work equally well on a 20-30 year old Nokia phone.
As I'll explain in more detail below, I did setup a quick Node-RED flow to consume these events as shown in the video below:
Video
Here you can see that:
- The light changes to cool white when the call is initiated (phone not yet ringing)
- It turns pink when the phone is ringing
- It turns red when we pick up the call
- It returns to normal after the call has ended
Connecting to wgtwo's API
We will use a normal OAuth2 authorization code grant for logging in to our service.
To handle this, we used the module github.com/markbates/goth
with the following settings:
import "golang.org/x/oauth2"
const endpointProfile string = "https://id.wgtwo.com/userinfo"
var Endpoint = oauth2.Endpoint{
AuthURL: "https://id.wgtwo.com/oauth2/auth",
TokenURL: "https://id.wgtwo.com/oauth2/token",
}
When logging in, the user will be asked to consent to the following scopes:
- phone: Allowing the service to fetch the user’s phone number
- offline_access: Giving the service a refresh token
- events.voice.subscribe: Allow the service to see all call events
- events.voicemail.subscribe: Allow the service to see if a voicemail has been left
- events.sms.subscribe: Allow the service to get a copy of every SMS sent and received
All the events you have consented to share with the service will be stored in the service's queue.
sms events ─╮
voice events ─┼─▷ queue ◁── gRPC API
voice mail events ─╯
This queue can be consumed by using the events streaming API (docs), which requires the service to use the OAuth2 client credentials grant flow.
Events will be shared with the service as long as there exists an active consent.
We then initiate the server side stream to fetch the events:
request := &pb.SubscribeEventsRequest{
Type: []pb.EventType{pb.EventType_VOICEMAIL_EVENT},
StartPosition: &pb.SubscribeEventsRequest_StartAtOldestPossible{},
ClientId: uuid.New().String(),
QueueName: "wgtwo-mqtt-demo",
DurableName: "wgtwo-mqtt-demo",
MaxInFlight: 10,
ManualAck: &pb.ManualAckConfig{
Enable: true,
Timeout: ptypes.DurationProto(10 * time.Second),
},
}
r, err := c.Subscribe(context.TODO(), request)
for {
response, err := r.Recv()
if err == io.EOF || err != nil {
break
}
event := response.Event
// PUBLISH EVENT TO MQTT SERVER ON TOPIC {event owner}/events/{type}
}
Connecting to our new MQTT service
The service has a very pretty landing page (Disclaimer: I am not a designer).
Clicking this button takes you to the login page:
… and then asks you to allow our service to get your voice event and new voicemails. As I am only interested in the voice events here, I’ll only grant that.
As this is an experimental app which hasn’t been approved by anyone, our login page will give you a clear warning about trusting this.
When that is done, it returns to our app showing this beautiful UI (still not a designer):
The generated credentials will allow you to listen any topic matching {phone number}/#
.
The following is the output from pasting that mosquitto_sub command in my terminal. It shows that I first called my Swedish number and hanging up before it was actually ringing.
{"event":{"metadata":{"sequence":"1","ackInbox":"_INBOX.VMTx7rnS0i3qXpHfuS5t3b"},"timestamp":"2021-01-06T11:24:40Z","serviceId":"wotel","voiceEvent":{"callId":"0c056e2c-07f9-4c2b-b5ca-042f160af42f","type":"CALL_INITIATED","fromNumber":{"e164":"+4712345678"},"toNumber":{"e164":"+46123456789"},"owner":{"e164":"+46123456789"}}}}
{"event":{"metadata":{"sequence":"2","ackInbox":"_INBOX.VMTx7rnS0i3qXpHfuS5t3b"},"timestamp":"2021-01-06T11:24:43Z","serviceId":"wotel","voiceEvent":{"callId":"0c056e2c-07f9-4c2b-b5ca-042f160af42f","type":"CALL_ENDED","fromNumber":{"e164":"+4712345678"},"toNumber":{"e164":"+46123456789"},"owner":{"e164":"+46123456789"}}}}
If you run any home automation or other hobby projects at home, chances are that you already have a MQTT broker running. You could then setup bridging to not worry about credentials and TLS when consuming your events.
Wrapping it all up
For this project I chose to use Node-RED, as it allows for very quick and easy to show drag-and-drop integrations.
To control the lamp, we did add the module node-red-contrib-tradfri
as described in the Node-RED documentation.
First we did add a mqtt out
node configured to listen to the topic 46123456789/# with output as a parsed JSON object using the credentials we got on login.
We then simply hooked its output to a debug node. Looking at the output, we can see that the event object has a key voiceEvent
, as this is a voice event.
Then we added a switch for handling it as a voice event if the voiceEvent key exists. Likewise, we added a new switch on the type field of that event.
Each of those functions simply set the Trådfri payload, as shown below:
{"state":"on","color":"cool daylight"}
As this was created quickly as a hackathon project, the intention was never to actually make anything useful. Using this quick flow, it is however clear that it could be very useful for when my wife tries to call me, but I am programming equipped with my noise-cancelling headphones.