I needed to turn my iPhone into an alarm that gets triggered by my XBee / Arduino / PIR sensor rig. (Beware, person breaking in to my garage.)

An SMS or voice call wasn’t loud enough to wake me up when my iPhone is plugged in to my pillow speaker. I needed something louder. Apple’s iCloud “find my phone” feature fit the bill. It makes the phone emit a loud alarm through the external speakers regardless of whether or not the headphones (or pillow speakers) are plugged in.

However, doing this programmatically was a challenge.

The Technique

To figure this out, I logged on to https://www.icloud.com/ and used the web interface to initiate “Play Sound” on my phone. I used Chrome’s “Copy as cURL” to inspect, tweak and replay the requests so that I could figure out precisely what’s important and what’s not.

It turns out that a number of things need to happen to trigger the “play sound” request:

  1. Authenticate with your apple id and password.
  2. Get the “findme” web service URL.
  3. Retrieve the device’s ID.
  4. Call the play sound API passing in the device ID.

Throughout the sequence of HTTP requests, it’s important to capture the cookies being set in the response headers and pass them on during subsequent requests.

The device name (e.g., “Chad’s iPhone”) is what appears in the web interface. The device ID is a random looking string that identifies the device for the play sound request.

The Code

I’ve paired this down to what I believe to be the minimal code required to trigger “play sound” on a device:

require 'httparty'
require 'json'

class RbiCloud
  include HTTParty

  def play_sound(apple_id, password, device_name)
    cookies = CookieHash.new

    response = self.class.post "https://setup.icloud.com/setup/ws/1/login",
      verify: false,
      headers: {
        "Origin" => "https://www.icloud.com"
      },
      body: {
        apple_id: apple_id,
        password: password
      }.to_json

    cookies.add_cookies(response.headers["Set-Cookie"])

    url = JSON.parse(response.body)["webservices"]["findme"]["url"]

    response = self.class.post "#{url}/fmipservice/client/web/initClient",
      verify: false,
      headers: {
        "Origin" => "https://www.icloud.com",
        "Cookie" => cookies.to_cookie_string
      }

    cookies.add_cookies(response.headers["Set-Cookie"])

    devices = Hash[
      JSON.parse(response.body)["content"].collect do |device|
        [ device["name"], device["id"] ]
      end
    ]

    device_id = devices[device_name]

    response = self.class.post "#{url}/fmipservice/client/web/playSound",
      verify: false,
        headers: {
          "Origin" => "https://www.icloud.com",
          "Cookie" => cookies.to_cookie_string
        },
        body: {
          device: device_id,
          subject: "Find My iPhone Alert"
        }.to_json
  end
end

RbiCloud.new.play_sound("user@example.com", "password", "Chad's iPhone")