Android Phone Finder from OpenHAB (Google Assistant and Alexa)

Goal:

  • Use voice command (Google Assistant and/or Alexa) to initiate Android phone finder feature
    • “Hey Google, turn on Jim’s Phone Finder”
    • “Alexa, turn on Bob’s Phone Finder”
  • Programmatically initiate phone finder feature

When you have multiple android phones, asking Google to find my phone results in it prompting about each phone one by one. It can get quite tedious. Sometimes it would just ring the first phone.

This solution lets you ring the specific phone using an openhab item.

Requirements:

  • selenium standalone docker
  • JRuby scripting (it can probably be done in other language too)

Selenium Docker Setup

Using docker-compose.yml:

version: "3"
services:
  selenium:
    container_name: selenium
    image: selenium/standalone-chrome
    restart: always
    shm_size: 2gb
    ports:
      - "4444:4444"

Pull the image and start the docker:

$ docker-compose pull selenium
$ docker-compose up -d selenium

Items setup

Create openhab items to receive command and be linked to Google Assistant and Alexa

Group   gPhoneFinders

Switch Find_Jims_Phone "Jim's Phone Finder" (gPhoneFinders) {phone="Jim's Phone", expire="3s", ga="Switch", alexa="Switch"}
Switch Find_Bobs_Phone "Bob's Phone Finder" (gPhoneFinders) {phone="Bob's Phone", expire="3s", ga="Switch", alexa="Switch"}

The phone metadata should contain the phone’s name as displayed on your Find my phone page

Don’t forget to tell Google to Sync my devices and Alexa to Discover devices

Obtain your google credential cookies

  • Open your browser, and go to www.google.com
  • Enable “Developer tool” of your browser
  • Refresh the google page
  • Go to `Network" tab and find the first item. This should be the request to www.google.com
  • Find the request header and copy the cookies. It should look like “SIDCC=xxxxx; __Secure-3PSIDCC=xxxxx; blahblah=xxxxx”
  • Paste that into the COOKIES in the code below

Rules

Using JRuby:

require 'openhab'
require 'json'
require 'cgi/cookie'

gemfile do
  source 'https://rubygems.org'
  gem 'selenium-webdriver'
end

GOOGLE_HOME_URL = 'https://www.google.com'
GOOGLE_FIND_URL = 'https://www.google.com/search?q=find+my+phone'

COOKIE_FILE = __conf__ + 'scripts/findmyphone-google-cookie.json'
COOKIES = 'xxxx' # paste your google cookies here

class FindMyPhone
  def initialize(server = '127.0.0.1', port = 4444)
    options = Selenium::WebDriver::Options.chrome
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-gpu')

    @driver = Selenium::WebDriver.for :remote, url: "http://#{server}:#{port}/wd/hub", options: options
    @driver.manage.delete_all_cookies
  end

  def close
    @driver.manage.delete_all_cookies
    @driver.quit
  end

  def logged_in?
    return false unless @driver.title.include? 'Google'

    login_btn = @driver.find_elements(xpath: "//a[contains(@href,'ServiceLogin')]")

    if login_btn.nil? || login_btn.empty?
      save_cookies
      return true
    end

    false
  end

  def set_cookies(new_cookies)
    CGI::Cookie.parse(new_cookies).each do |name, cookie|
      cookie.value.each { |value| @driver.manage.add_cookie(name: name, value: value) }
    end
  end

  def save_cookies
    File.write(COOKIE_FILE, @driver.manage.all_cookies.to_json)
  end

  def load_cookies
    return unless File.exist? COOKIE_FILE

    JSON.parse(File.read(COOKIE_FILE))
        .each { |cookie| @driver.manage.add_cookie(name: cookie['name'], value: cookie['value']) }
  end

  def refresh
    @driver.navigate.refresh
  end

  def home
    @driver.get(GOOGLE_HOME_URL)
  end

  def find
    @driver.get(GOOGLE_FIND_URL)
  end

  def refresh_cookies
    home
    load_cookies
    refresh
    unless logged_in?
      logger.info 'Not logged in, setting initial cookies'
      set_cookies COOKIES
      refresh
    end
    if logged_in?
      save_cookies
      logger.info 'Cookies refreshed'
    else
      logger.warn 'Unable to login. Please update the cookies'
    end
  end

  def ring(phone)
    find

    dropdown_btn = @driver.find_element(tag_name: '.gws-action__act-device-label')
    dropdown_btn.click

    device = dropdown_btn.find_element(xpath: %(//*[contains(text(), "#{phone}")]))
    device.click

    ring_button = @driver.find_element(id: 'act-ring-link')
    ring_button.click
    sleep 1
  end
end

rule 'Find my phone: Refresh cookies' do
  every 3.hours
  run do
    fmp = FindMyPhone.new
    fmp.refresh_cookies
  ensure
    fmp.close
  end
end

rule 'Find my phone: Ring' do
  received_command gPhoneFinders.members, command: ON
  triggered do |item|
    fmp = FindMyPhone.new
    fmp.refresh_cookies
    fmp.ring item.meta['phone'].value
  ensure
    fmp.close
  end
end

Note:

  • this assumes that the Selenium docker is running on the same machine as openhab. If not, adjust the server parameter in the constructor.

  • This was inspired by GitHub - lukeIam/MqttFindMyPhone: Triggers Android FindMyPhone requests via mqtt

  • Need to implement a mutex that serialises the call to selenium to prevent simultaneous finding

  • The cookies are saved in CONF/scripts/ - this contains sensitive information that will allow login to your google account.

3 Likes