SiriProxy plugin for Indigo

Posted on
Fri Jan 25, 2013 8:16 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

Good progress is being made... items 2 and 3 in the list above are working. I am just trying to figure out the auth and it's giving me some issues. Stay tuned!

Matt

Posted on
Fri Jan 25, 2013 1:22 pm
nexx offline
Posts: 56
Joined: Jun 27, 2010

Re: SiriProxy plugin for Indigo

mreyn2005 wrote:
Good progress is being made... items 2 and 3 in the list above are working. I am just trying to figure out the auth and it's giving me some issues. Stay tuned!

Matt


Good news!!!

Posted on
Sun Jan 27, 2013 2:00 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

EDIT: Update - All code is posted here: https://github.com/msreynolds/siriproxy-violet/

Here is siriproxy-violet 1.0 beta rc1, complete with support for digest auth. You'll have to build your credentials into the .rb file, but (hopefully) that isn't a concern for too many folks. I think most everything in the script is pretty self explanatory if you give it a look over. If you don't want auth at all just comment out the 2 lines:

Code: Select all
http_request.add_field 'Authorization', auth


Otherwise, let me know if you guys have any feedback, I am sure there are improvements to be made! If someone out there wants BASIC support let me know and I *might* implement it if you're super nice and say please 3 times.


siriproxy-violet.gemspec
Code: Select all
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)

Gem::Specification.new do |s|
  s.name        = "siriproxy-violet"
  s.version     = "1.0"
  s.authors     = ["Matthew Reynolds, Mountain Labs"]
  s.email       = ["matt@mtnlabs.com"]
  s.homepage    = "mtnlabs.com"
  s.summary     = %q{SiriProxy plugin for controlling the Indigo Automation Server}
  s.description = %q{This plugin can control your Indigo system through voice control phrases issued to SiriProxy}

  s.rubyforge_project = ""

  s.files         = `git ls-files 2> /dev/null`.split("\n")
  s.test_files    = `git ls-files -- {test,spec,features}/* 2> /dev/null`.split("\n")
  s.executables   = `git ls-files -- bin/* 2> /dev/null`.split("\n").map{ |f| File.basename(f) }
  s.require_paths = ["lib"]

  # specify any dependencies here; for example:
  s.add_runtime_dependency "addressable"
  s.add_runtime_dependency "net-http-digest_auth"

end



siriproxy-violet.rb
Code: Select all
########################################################################################################################
# Mountain Labs, LLC
# mtnlabs.com
# Author: Matthew Reynolds
# matt at mtnlabs dot com
# Indigo Violet SiriProxy plugin 1.0 beta for controlling your Indigo Home Automation server
########################################################################################################################

require 'cora'
require 'siri_objects'
require 'pp'
require 'addressable/uri'
require 'net/http'
require 'net/http/digest_auth'

class SiriProxy::Plugin::Violet < SiriProxy::Plugin

  ##  Pick something that is:
  #1) the most common spelling of an actual name or word
  #2) a name or word that is easy to recognize and has a sharp pronunciation (violet does not work well)
  BOT_NAME = 'Indigo Violet'

  # Indigo Host (address and port of your Indigo Restful Interface)
  INDIGO_WEB_SERVER = 'http://127.0.0.1:8176'

  # Indigo Digest Auth properties
  INDIGO_REALM = 'Indigo Control Server'
  INDIGO_USER = 'indigo'
  INDIGO_PASSWORD = 'indigo'

  INDIGO_SPRINKLER_DEVICE = 'irrmasterpro'
  INDIGO_THERMOSTAT_DEVICE = 'thermostat'

  def initialize(config)
    #process custom configuration options here!
  end

  #############################################################
  # Methods
  #############################################################

  # Create a device uri string
  # Return the device uri string
  def get_device_url(device_name)
    device_url = '/devices/'+device_name
    device_url = Addressable::URI.parse(device_url).normalize.to_str
    device_url
  end

  # Create an instance of URI
  # Return the URI instance
  def get_device_uri(device_name)
    device_url = INDIGO_WEB_SERVER + get_device_url(device_name)
    device_uri = Addressable::URI.parse(device_url)
    device_uri.user = INDIGO_USER
    device_uri.password = INDIGO_PASSWORD
    device_uri
  end

  # Create an action group uri string
  # Return the action group uri string
  def get_action_group_url(action_group_name)
    action_group_url = '/actions/'+action_group_name+'?_method=execute'
    action_group_url = Addressable::URI.parse(action_group_url).normalize.to_str
    action_group_url
  end

  # Create an instance of URI
  # Return the URI instance
  def get_action_group_uri(action_group_name)
    action_group_url = INDIGO_WEB_SERVER + get_action_group_url(action_group_name)
    action_group_uri = Addressable::URI.parse(action_group_url)
    action_group_uri.user = INDIGO_USER
    action_group_uri.password = INDIGO_PASSWORD
    action_group_uri
  end

  # Create and send an authenticated HTTP Get call
  # Return the HTTP Response
  def do_authenticated_get(indigo_uri)
    http = Net::HTTP.new indigo_uri.host, indigo_uri.port
    #http.set_debug_output $stderr

    # Make initial request that should produce a 401 Unauthorized
    # The 401 Unauthorized response contains important auth information,
    # bits of which are used later to create an authorized session
    http_request = Net::HTTP::Get.new indigo_uri.request_uri
    http_response = http.request http_request

    # Support for Digest Authentication
    digest_auth = Net::HTTP::DigestAuth.new

    # Clean the quotes from the algorithm=\"MD5\" in the original http_response's www-authenticate string
    # The spec does not include quotes in this flag, having them can cause issues
    # http://stackoverflow.com/questions/10770478/unknown-algorithm-md5-using-net-http-digest-auth
    www_auth_response = http_response['www-authenticate']
    www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"

    # Construct the appropriate Authentication header string
    auth = digest_auth.auth_header indigo_uri, www_auth_response, 'GET'

    # Create the new EXECUTE request with the proper Authorization header
    http_request = Net::HTTP::Get.new indigo_uri.request_uri
    http_request.add_field 'Authorization', auth

    # Make the second (authenticated) call, return the http response
    http.request http_request
  end

  # Create and send an authenticated HTTP PUT call
  # Return the HTTP Response
  def do_authenticated_put(indigo_uri, body)
    http = Net::HTTP.new indigo_uri.host, indigo_uri.port
    #http.set_debug_output $stderr

    # Make initial request that should produce a 401 Unauthorized
    # The 401 Unauthorized response contains important auth information,
    # bits of which are used later to create an authorized session
    http_request = Net::HTTP::Get.new indigo_uri.request_uri
    http_response = http.request http_request

    # Support for Digest Authentication
    digest_auth = Net::HTTP::DigestAuth.new

    # Clean the quotes from the algorithm=\"MD5\" in the original http_response's www-authenticate string
    # The spec does not include quotes in this flag, having them can cause issues
    # http://stackoverflow.com/questions/10770478/unknown-algorithm-md5-using-net-http-digest-auth
    www_auth_response = http_response['www-authenticate']
    www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"

    # Construct the appropriate Authentication header string
    auth = digest_auth.auth_header indigo_uri, www_auth_response, 'PUT'

    # Create the new PUT request with the proper Authorization header
    http_request = Net::HTTP::Put.new indigo_uri.request_uri
    http_request.content_type = 'multipart/form-data'
    http_request.set_form_data(body)
    http_request.add_field 'Authorization', auth

    # Make the second (authenticated) call, return the http response
    http.request http_request
  end

  # Create a string response to send back to the user, given the HTTP response from the indigo web server
  # Return the string
  def get_bot_response(http_response)
    case http_response.code
      when "303"
        "Ok"
      when "401"
        "I am unauthorized"
      when "200"
        "I am unable to find that device"
      when "404"
        "I am unable to find the automation system"
      else
        "I received an unknown response from the automation system"
    end
  end


  #############################################################
  # Filters
  #############################################################

  #get the user's location and display it in the logs
  #filters are still in their early stages. Their interface may be modified
  filter "SetRequestOrigin", direction: :from_iphone do |object|
    puts "[Info - User Location] lat: #{object["properties"]["latitude"]}, long: #{object["properties"]["longitude"]}"

    #Note about returns from filters:
    # - Return false to stop the object from being forwarded
    # - Return a Hash to substitute or update the object
    # - Return nil (or anything not a Hash or false) to have the object forwarded (along with any
    #    modifications made to it)
  end

  ## Get the device's unique Siri ID so we know who's making the request.
  #filter "LoadAssistant", direction: :from_iphone do |object|
  #  @assistantId = object["properties"]["assistantId"]
  #  puts "[Info - Assistant ID: #{@assistantId}"


  #############################################################
  # Listen Control Phrases
  #############################################################

  listen_for /test siri proxy/i do
    # standard test
    say "You may call me "+BOT_NAME
    request_completed
  end

  listen_for /test #{BOT_NAME}/i do
    # custom test
    begin
      devices_url = INDIGO_WEB_SERVER + '/devices.xml'
      devices_uri = Addressable::URI.parse(devices_url)
      devices_uri.user = INDIGO_USER
      devices_uri.password = INDIGO_PASSWORD
      http_response = do_authenticated_get(devices_uri)
      case http_response.code
        when "401"
          status = "Unauthorized"
        when "200"
          status = "Ready"
        when "404"
          status = "Unable to find the automation system"
        else
          status = "Unable to negotiate with the automation system"
      end
      say 'Indigo Violet plugin 1.0 beta for SiriProxy. System Status: '+status
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  listen_for /hello #{BOT_NAME}/i do
    # standard greeting
    say 'Hello.'
    request_completed
  end

  #Turn On
  listen_for /turn on(?: the)? ([a-z 1-9]*)/i do |device_name|
    device_name.strip!
    begin
      device_uri = get_device_uri(device_name)
      http_response = do_authenticated_put(device_uri, {"isOn" => "True"})
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Turn Off
  listen_for /turn off(?: the)? ([a-z 0-9]*)/i do |device_name|
    device_name.strip!
    begin
      device_uri = get_device_uri(device_name)
      http_response = do_authenticated_put(device_uri, {"isOn" => "False"})
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Execute
  listen_for /execute ([a-z 0-9]*)/i do |action_group_name|
    action_group_name.strip!
    begin
      action_group_uri = get_action_group_uri(action_group_name)
      http_response = do_authenticated_get(action_group_uri)
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Thermostat
  listen_for /set(?: the)? thermostat to ([0-9]*[0-9])/i do |degrees|
    degrees.strip!
    begin
      device_uri = get_device_uri(INDIGO_THERMOSTAT_DEVICE)
      http_response = do_authenticated_put(device_uri, {"setpointHeat" => degrees})
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Sprinklers
  listen_for /([a-z]*) sprinklers/i do |action|
    action.strip!
    begin
      if (action =~ /pause/i or action =~ /resume/i)
        device_uri = get_device_uri(INDIGO_SPRINKLER_DEVICE)
        http_response = do_authenticated_put(device_uri, {"activeZone" => action})
        say get_bot_response(http_response)
      else
        say "I don't understand that sprinkler action request"
      end
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Brightness
  listen_for /set(?: the)? ([a-z 0-9]*) brightness at ([0-9]*[0-9])(?: percent)?/i do |device_name, dim_value|
    device_name.strip!
    dim_value.strip!
    begin
      device_uri = get_device_uri(device_name)
      http_response = do_authenticated_put(device_uri, {"brightness" => dim_value})
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

  #Toggle
  listen_for /toggle(?: the)? ([a-z 1-9]*)/i do |device_name|
    device_name.strip!
    begin
      device_uri = get_device_uri(device_name)
      http_response = do_authenticated_put(device_uri, {"toggle" => "1"})
      say get_bot_response(http_response)
    rescue Exception=>e
      puts e.exception
    ensure
      request_completed
    end
  end

end
Last edited by mreyn2005 on Tue Jan 29, 2013 6:41 pm, edited 3 times in total.

Posted on
Sun Jan 27, 2013 2:37 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

I notice that, in certain cases, it seems to get confused if you make two requests in a row quickly from the same siri session... the second request often just hangs. Mostly it works ok though :-) I'm on the hunt.

Matt
Last edited by mreyn2005 on Sun Jan 27, 2013 10:07 am, edited 1 time in total.

Posted on
Sun Jan 27, 2013 3:04 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

From the SiriProxy readme: https://github.com/plamoni/SiriProxy/bl ... /README.md

Using Siri causes a whole bunch of the following messages, followed by SiriProxy crashing!

Create server for iPhone connection
start conn #<SiriProxy::Connection::Iphone:0x966a400 @signature=880, @processed_headers=false, @output_buffer="", @input_buffer="", @unzipped_input="", @unzipped_output="", @unzip_stream=#<Zlib::Inflate:0x9669640>, @zip_stream=#<Zlib::Deflate:0x96695dc>, @consumed_ace=false, @name="iPhone", @ssled=false>
[Info - Plugin Manager] Plugins laoded: [#<SiriProxy::Plugin::Example:0x968a818 @manager=#<SiriProxy::PluginManager:0x9685750 @plugins=[...]>>]
This is actually really common (but can be tricky to fix). The problem is that your SiriProxy server is using your tainted DNS server. So what happens is this:

Your iPhone connects to your server, thinking it's guzzoni.apple.com
Your server connects to itself, thinking that it's guzzoni.apple.com
Your server thinks another iPhone has connected, and repeats step 2.
This goes on forever, or at least a second or two before the server up and dies. The trick is that you need to make sure your server isn't connecting to itself when it requests a connection to guzzoni.apple.com. This is actually the default behavior, but many people accidentally mess things up by either (1) setting up their server to use itself as a DNS server (while using dnsmasq to taint the entry for guzzoni.apple.com), or (2) putting their server on a network where the DNS server issued by DHCP is tainted to point to the wrong guzzoni.apple.com.

So the fix for this varies based on your setup, but one possible fix for scenario 1 (above) on many *NIX machines is to edit /etc/resolve.conf and change the nameserver entry to 8.8.8.8 (one of Google's public DNS servers). Do this and then restart networking (or just restart the computer) and things should start working.

Your network setup may be different. This is THE most complex part of setting up SiriProxy (getting DNS set up correctly). So once you have this working, you are probably home free. Keep with it, good luck, and have fun!


I think this is what is going on.

Matt

Posted on
Tue Jan 29, 2013 6:41 pm
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

Things are pretty stable when you don't start and stop SiriProxy 30 times in 2 minutes :-p

The codez now live here: https://github.com/msreynolds/siriproxy-violet/
This is where future code updates will be posted as well.

Cheers!
Matt

Posted on
Fri Feb 01, 2013 10:03 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

I am planning on moving the configuration settings (host, user, pass, etc) into the SiriProxy config.yaml file as I have seen other plugins do.

Anyone try using this yet? Any feedback?

Cheers!
Matt

Posted on
Wed Feb 06, 2013 2:15 pm
Dewster35 offline
Posts: 1030
Joined: Jul 06, 2010
Location: Petoskey, MI

Re: SiriProxy plugin for Indigo

http://www.cultofmac.com/214647/siri-ca ... ack-video/

This post on cult of mac inspired me...

How complicated is this for someone with limited programming background to get the commands setup?

Posted on
Thu Feb 07, 2013 9:18 am
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

If you can follow the tutorial I posted a link to previously (from Funky Space Monkey's blog), you should be able to just paste the contents of the siriproxy-violet.gemspec and siriproxy-violet.rb files into their respective parts in the example plugin that comes with SiriProxy. Be sure to adjust your Indigo settings currently found in the .rb file. When you do that, you need to do the last two steps in the tutorial, steps 24 and 25 (to "re-bundle" SiriProxy with your changes to the default plugin). At that point, SiriProxy should be running, the default plugin that comes with SiriProxy will have the guts of the violet plugin in it, and you should be able to use the plugin from your phone, so long as you've followed the bits of the tutorial about getting the generated certificate onto your phone and pointing your phone's Wifi DNS to the machine running SiriProxy.

It's that easy! Clear as mud? :-)

Matt
Last edited by mreyn2005 on Thu Feb 07, 2013 11:32 am, edited 1 time in total.

Posted on
Thu Feb 07, 2013 10:30 am
Dewster35 offline
Posts: 1030
Joined: Jul 06, 2010
Location: Petoskey, MI

Re: SiriProxy plugin for Indigo

Definitely a weekend long project :) Thanks... I'll give it a whirl!

Posted on
Sat May 18, 2013 12:42 pm
brianmaas offline
Posts: 23
Joined: Oct 05, 2010

Re: SiriProxy plugin for Indigo

I've been playing with this for a while but still can't seem to get it to work. Test Siri Proxy command works. It tells me it wants to be called "Indigo" which is what i set.

I don't need the auth so i commented out the single line you said.

Any command that needs to access the server i get this error:
Code: Select all
[Info - Guzzoni] Received Object: SpeechRecognized
[Info - Plugin Manager] Processing 'Toggle kitchen light'
[Info - Plugin Manager] Processing plugin Example
[Info - Plugin Manager] Matches (?i-mx:toggle(?: the)? ([a-z 1-9]*))
[Info - Plugin Manager] Applicable states:
[Info - Plugin Manager] Current state:
[Info - Plugin Manager] Matches, executing block
undefined method `[]=' for nil:NilClass
[Info - Plugin Manager] Sending Request Completed


Any ideas of what this could be or should i send all the code?

Brian

Posted on
Tue May 21, 2013 10:02 pm
brianmaas offline
Posts: 23
Joined: Oct 05, 2010

Re: SiriProxy plugin for Indigo

I've been trying lots of things but can't figure out the undefined method error. I also don't know what jibberish i'm getting back in the logs. Nothing should be encrypted and I don't use authentication on my LAN for indigo.

I uncommented some debug lines. This is what I get:
Code: Select all
[Info - iPhone] Received Object: SpeechPacket
[Info - iPhone] Received Object: FinishSpeech
[Info - Guzzoni] Received Object: SpeechRecognized
[Info - Plugin Manager] Processing 'Toggle the kitchen lights'
[Info - Plugin Manager] Processing plugin Example
[Info - Plugin Manager] Matches (?i-mx:toggle(?: the)? ([a-z 1-9]*))
[Info - Plugin Manager] Applicable states:
[Info - Plugin Manager] Current state:
[Info - Plugin Manager] Matches, executing block
opening connection to 192.168.1.30:8176...
opened
<- "GET /devices/kitchen%20lights HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: 192.168.1.30:8176\r\n\r\n"
-> "HTTP/1.1 200 OK\r\n"
-> "Content-Length: 655\r\n"
-> "Content-Encoding: gzip\r\n"
-> "Vary: Accept-Encoding\r\n"
-> "Server: IndigoWebServer/5.0\r\n"
-> "Allow: GET, HEAD, POST, PUT\r\n"
-> "Date: Wed, 22 May 2013 03:45:41 GMT\r\n"
-> "Content-Type: text/html;charset=utf-8\r\n"
-> "Connection: close\r\n"
-> "\r\n"
reading 655 bytes...
-> "\x1F\x8B\b\x00e?\x9CQ\x02\xFF\x95\x95_s\x9A@\x14\xC5\x9F\x93O\xB1e&o\x85\xE5\x8F\x18\xB1\xE8L\x82\xDA8\xA3\xD5\t\xA4i\xFB\xB6\xC2Uv\x82\x8B\xD9]c\xFC\xF6]\xD0\xB4\x1DP\xAB/\nx~\xE7pw\xBDw\xFDO\xBDI\x10\xFD\x9C\xF6Q*\x97\x19\x9A>\xDD\x8F\x86\x01\xD2t\x8C\x9F\x9D\x00\xE3^\xD4C?\x1E\xA2\xF1\bY\x86\x89B\xC9i,1\xEE\x7F\xD3\x90\x96J\xB9jc\xBC\xD9l\x8C\x8Dc\xE4|\x81\xA3G\xFC^\xB8X\x05\xB6\xBF\xD4E\xC9\x18\x89L\xB4\xEE\xB5_\x86\xBC/3&:\a\f,\xCF\xF3v\x9CV\x88\xDA\x19a\x8B\x8E\x06L\x91W~\n$Q\xDFW\xBE\xA42\x83\xEE\x90%t\x91\xA3\x87|\t(\xC8\x99\xE4y\x86B\xE0o\xC0}\xBCS\x14\xDA%H\x82\x8A \x1D^\xD7\xF4\xAD\xA3\x15R`R\x8F\xB6+\xD0P\xBC\xBB\xEBh\x12\xDE%.\x82\xBF\xA08%\\\x80\xEC\xAC\xE5\\oi\b\x17\xD9x\x1F\xEE\xCF\xF2d[\x1A\xAB\xB2r\xB6\xE82\xA2\xE2\xDB\xC8\xC7\xFB{\xF5\xD3\v\x95q\n\fet\x91J\xE1\xCFxi\xF1\x87 I\xC2A\x88\nd5\xCCF\xEB\xD6:\xA6V\v_\x05\\\xA390\x82\xDB\x1A1\xE3E.;\x10a\x9A5q\x9C\x11!\x86\xBD\x8A\xD2\xAE\xE9\x12\x10qE4\xDB\xAA\xD5\x9B\xCF\x01\x0E\x88\xDF\xA6<\x97y\xACv\xA4\xF2\nu-\x15\xAB\x8Cl\x87\xECiX\xD1F|\r\xC7\xE4#u\x1DJ\"\xA1^\xE2\xCD1\xE6\x91l\x8E 5\"%\xA2\x94Fyo\xC7\x9E\xF3j4\xA9\xA8\\\xBBe5]\xDB\xA9\xFBS1a\xE7X\xAA\xCD\x91A\xAAz\x00\xAA\xDE\r\xDBn\xDCZM\xCF;\xC5\xF4T\t\xF5?\x8EmZ\x8En\xBA\xBAm\x9Db\x1F\a\x81\xE38\xDE1\xD6\x8EL\xB3m5\xDA\xAE\xF7\xEB?.-\xDB\xAE\x98<C\xF2\x19\xA9\xA7c\xB5\xAE\x85!\xFA\xB0B_\xC7\xD1)\xB7\x88.\x0F\xD4c\xBA{z:\xAE\xC1R5yE\x1En\x8A\xF6\x1CQ\x16\xA3\x1E].\xD5\xB48\x04\r2\xB2\xA8\xF5\x8FcY\a\xC5\xE1z\xB5\xCA\xB9\x14\xCA\xEF\x9C]\xFD\x17\xE93\xE0\x8B\xED\x18$T\xCB\x1A\x90L\x9Cf\x1F\xBE\xDF\x05\x17C\xC3\xC9\xC5\xC8\x84M\xE6\xF3K\v\vW\x00\xC9\xC7X\xBE41\\q\xCA^\xB23\xD7DM|q\xBF\xAD5\xB6\xDB\xFC\xABTG\x00\x87yG\xC3j4\xD1\x18\x04\xDE\xCF\xE8\e\xDB\xDCMi\xA3\x18\xFD\x18\x12*\xB5n_}\xA2;\xA9\x8E\xAD\xD9Z\x82\xF01)O\x80\xDD\xE4\xF7\xCBC\xA2{\xFD\e\x02\xEFn|7\a\x00\x00"
read 655 bytes
Conn close
undefined method `[]=' for nil:NilClass
[Info - Plugin Manager] Sending Request Completed



These are my files in siriproxy. I've just added lines to the example plugin to make it work.

siriproxy-example.gemspec
Code: Select all
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)

Gem::Specification.new do |s|
  s.name        = "siriproxy-example"
  s.version     = "0.0.1"
  s.authors     = ["plamoni"]
  s.email       = [""]
  s.homepage    = ""
  s.summary     = %q{An Example Siri Proxy Plugin}
  s.description = %q{This is a "hello world" style plugin. It simply intercepts the phrase "text siri proxy" and responds with a message about the proxy being up and running. This is good base code for other plugins. }

  s.rubyforge_project = "siriproxy-example"

  s.files         = `git ls-files 2> /dev/null`.split("\n")
  s.test_files    = `git ls-files -- {test,spec,features}/* 2> /dev/null`.split("\n")
  s.executables   = `git ls-files -- bin/* 2> /dev/null`.split("\n").map{ |f| File.basename(f) }
  s.require_paths = ["lib"]

  # specify any dependencies here; for example:
  # s.add_development_dependency "rspec"
  # s.add_runtime_dependency "rest-client"
    s.add_runtime_dependency "addressable"
    s.add_runtime_dependency "net-http-digest_auth"

end



siriproxy-example.rb
Code: Select all
require 'cora'
require 'siri_objects'
require 'pp'
require 'addressable/uri'
require 'net/http'
require 'net/http/digest_auth'

#######
# This is a "hello world" style plugin. It simply intercepts the phrase "test siri proxy" and responds
# with a message about the proxy being up and running (along with a couple other core features). This
# is good base code for other plugins.
#
# Remember to add other plugins to the "config.yml" file if you create them!
######

class SiriProxy::Plugin::Example < SiriProxy::Plugin
  def initialize(config)
    #if you have custom configuration options, process them here!
  end

  #get the user's location and display it in the logs
  #filters are still in their early stages. Their interface may be modified
  filter "SetRequestOrigin", direction: :from_iphone do |object|
    puts "[Info - User Location] lat: #{object["properties"]["latitude"]}, long: #{object["properties"]["longitude"]}"

    #Note about returns from filters:
    # - Return false to stop the object from being forwarded
    # - Return a Hash to substitute or update the object
    # - Return nil (or anything not a Hash or false) to have the object forwarded (along with any
    #    modifications made to it)
  end

  listen_for /where am i/i do
    say "Your location is: #{location.address}"
  end


  #listen_for /test siri proxy/i do
  #  say "Siri Proxy is up and running Brian!" #say something to the user!

  #  request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  #end


  #Demonstrate that you can have Siri say one thing and write another"!
  listen_for /you don't say/i do
    say "Sometimes I don't write what I say", spoken: "Sometimes I don't say what I write"
  end

  #demonstrate state change
  listen_for /siri proxy test state/i do
    set_state :some_state #set a state... this is useful when you want to change how you respond after certain conditions are met!
    say "I set the state, try saying 'confirm state change'"

    request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  end

  listen_for /confirm state change/i, within_state: :some_state do #this only gets processed if you're within the :some_state state!
    say "State change works fine!"
    set_state nil #clear out the state!

    request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  end

  #demonstrate asking a question
  listen_for /siri proxy test question/i do
    response = ask "Is this thing working?" #ask the user for something

    if(response =~ /yes/i) #process their response
      say "Great!"
    else
      say "You could have just said 'yes'!"
    end

    request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  end

  #demonstrate capturing data from the user (e.x. "Siri proxy number 15")
  listen_for /siri proxy number ([0-9,]*[0-9])/i do |number|
    say "Detected number: #{number}"

    request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  end

  #demonstrate injection of more complex objects without shortcut methods.
  listen_for /test map/i do
    add_views = SiriAddViews.new
    add_views.make_root(last_ref_id)
    map_snippet = SiriMapItemSnippet.new
    map_snippet.items << SiriMapItem.new
    utterance = SiriAssistantUtteranceView.new("Testing map injection!")
    add_views.views << utterance
    add_views.views << map_snippet

    #you can also do "send_object object, target: :guzzoni" in order to send an object to guzzoni
    send_object add_views #send_object takes a hash or a SiriObject object

    request_completed #always complete your request! Otherwise the phone will "spin" at the user!
  end




      ##  Pick something that is:
      #1) the most common spelling of an actual name or word
      #2) a name or word that is easy to recognize and has a sharp pronunciation (violet does not work well)
      BOT_NAME = 'Indigo'

      # Indigo Host (address and port of your Indigo Restful Interface)
      INDIGO_WEB_SERVER = 'http://192.168.1.30:8176'

      # Indigo Digest Auth properties
      INDIGO_REALM = 'Indigo Control Server'
      INDIGO_USER = 'indigo'
      INDIGO_PASSWORD = 'indigo'

      INDIGO_SPRINKLER_DEVICE = 'Sprinklers'
      INDIGO_THERMOSTAT_DEVICE = 'Thermostat Downstairs'

      def initialize(config)
        #process custom configuration options here!
      end

      #############################################################
      # Methods
      #############################################################

      # Create a device uri string
      # Return the device uri string
      def get_device_url(device_name)
        device_url = '/devices/'+device_name
        device_url = Addressable::URI.parse(device_url).normalize.to_str
        device_url
      end

      # Create an instance of URI
      # Return the URI instance
      def get_device_uri(device_name)
        device_url = INDIGO_WEB_SERVER + get_device_url(device_name)
        device_uri = Addressable::URI.parse(device_url)
        device_uri.user = INDIGO_USER
        device_uri.password = INDIGO_PASSWORD
        device_uri
      end

      # Create an action group uri string
      # Return the action group uri string
      def get_action_group_url(action_group_name)
        action_group_url = '/actions/'+action_group_name+'?_method=execute'
        action_group_url = Addressable::URI.parse(action_group_url).normalize.to_str
        action_group_url
      end

      # Create an instance of URI
      # Return the URI instance
      def get_action_group_uri(action_group_name)
        action_group_url = INDIGO_WEB_SERVER + get_action_group_url(action_group_name)
        action_group_uri = Addressable::URI.parse(action_group_url)
        action_group_uri.user = INDIGO_USER
        action_group_uri.password = INDIGO_PASSWORD
        action_group_uri
      end

      # Create and send an authenticated HTTP Get call
      # Return the HTTP Response
      def do_authenticated_get(indigo_uri)
        http = Net::HTTP.new indigo_uri.host, indigo_uri.port
        http.set_debug_output $stderr

        # Make initial request that should produce a 401 Unauthorized
        # The 401 Unauthorized response contains important auth information,
        # bits of which are used later to create an authorized session
        http_request = Net::HTTP::Get.new indigo_uri.request_uri
        http_response = http.request http_request

        # Support for Digest Authentication
        digest_auth = Net::HTTP::DigestAuth.new

        # Clean the quotes from the algorithm=\"MD5\" in the original http_response's www-authenticate string
        # The spec does not include quotes in this flag, having them can cause issues
        # http://stackoverflow.com/questions/10770478/unknown-algorithm-md5-using-net-http-digest-auth
        www_auth_response = http_response['www-authenticate']
        www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"

        # Construct the appropriate Authentication header string
        auth = digest_auth.auth_header indigo_uri, www_auth_response, 'GET'

        # Create the new EXECUTE request with the proper Authorization header
        http_request = Net::HTTP::Get.new indigo_uri.request_uri
        #http_request.add_field 'Authorization', auth

        # Make the second (authenticated) call, return the http response
        http.request http_request
      end

      # Create and send an authenticated HTTP PUT call
      # Return the HTTP Response
      def do_authenticated_put(indigo_uri, body)
        http = Net::HTTP.new indigo_uri.host, indigo_uri.port
        http.set_debug_output $stderr

        # Make initial request that should produce a 401 Unauthorized
        # The 401 Unauthorized response contains important auth information,
        # bits of which are used later to create an authorized session
        http_request = Net::HTTP::Get.new indigo_uri.request_uri
        http_response = http.request http_request

        # Support for Digest Authentication
        digest_auth = Net::HTTP::DigestAuth.new

        # Clean the quotes from the algorithm=\"MD5\" in the original http_response's www-authenticate string
        # The spec does not include quotes in this flag, having them can cause issues
        # http://stackoverflow.com/questions/10770478/unknown-algorithm-md5-using-net-http-digest-auth
        www_auth_response = http_response['www-authenticate']
        www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"

        # Construct the appropriate Authentication header string
        auth = digest_auth.auth_header indigo_uri, www_auth_response, 'PUT'

        # Create the new PUT request with the proper Authorization header
        http_request = Net::HTTP::Put.new indigo_uri.request_uri
        http_request.content_type = 'multipart/form-data'
        http_request.set_form_data(body)
        #http_request.add_field 'Authorization', auth

        # Make the second (authenticated) call, return the http response
        http.request http_request
      end

      # Create a string response to send back to the user, given the HTTP response from the indigo web server
      # Return the string
      def get_bot_response(http_response)
        case http_response.code
          when "303"
            "Ok"
          when "401"
            "I am unauthorized"
          when "200"
            "I am unable to find that device"
          when "404"
            "I am unable to find the automation system"
          else
            "I received an unknown response from the automation system"
        end
      end


      #############################################################
      # Filters
      #############################################################

      #get the user's location and display it in the logs
      #filters are still in their early stages. Their interface may be modified
      filter "SetRequestOrigin", direction: :from_iphone do |object|
        puts "[Info - User Location] lat: #{object["properties"]["latitude"]}, long: #{object["properties"]["longitude"]}"

        #Note about returns from filters:
        # - Return false to stop the object from being forwarded
        # - Return a Hash to substitute or update the object
        # - Return nil (or anything not a Hash or false) to have the object forwarded (along with any
        #    modifications made to it)
      end

      ## Get the device's unique Siri ID so we know who's making the request.
      #filter "LoadAssistant", direction: :from_iphone do |object|
      #  @assistantId = object["properties"]["assistantId"]
      #  puts "[Info - Assistant ID: #{@assistantId}"


      #############################################################
      # Listen Control Phrases
      #############################################################

      listen_for /test siri proxy/i do
        # standard test
        say "You may call me "+BOT_NAME
        request_completed
      end

      listen_for /test #{BOT_NAME}/i do
        # custom test
        begin
          devices_url = INDIGO_WEB_SERVER + '/devices.xml'
          devices_uri = Addressable::URI.parse(devices_url)
          devices_uri.user = INDIGO_USER
          devices_uri.password = INDIGO_PASSWORD
          http_response = do_authenticated_get(devices_uri)
          case http_response.code
            when "401"
              status = "Unauthorized"
            when "200"
              status = "Ready"
            when "404"
              status = "Unable to find the automation system"
            else
              status = "Unable to negotiate with the automation system"
          end
          say 'Indigo Violet plugin 1.0 beta for SiriProxy. System Status: '+status
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      listen_for /hello #{BOT_NAME}/i do
        # standard greeting
        say 'Hello.'
        request_completed
      end

      #Turn On
      listen_for /turn on(?: the)? ([a-z 1-9]*)/i do |device_name|
        device_name.strip!
        begin
          device_uri = get_device_uri(device_name)
          http_response = do_authenticated_put(device_uri, {"isOn" => "True"})
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Turn Off
      listen_for /turn off(?: the)? ([a-z 0-9]*)/i do |device_name|
        device_name.strip!
        begin
          device_uri = get_device_uri(device_name)
          http_response = do_authenticated_put(device_uri, {"isOn" => "False"})
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Execute
      listen_for /execute ([a-z 0-9]*)/i do |action_group_name|
        action_group_name.strip!
        begin
          action_group_uri = get_action_group_uri(action_group_name)
          http_response = do_authenticated_get(action_group_uri)
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Thermostat
      listen_for /set(?: the)? thermostat to ([0-9]*[0-9])/i do |degrees|
        degrees.strip!
        begin
          device_uri = get_device_uri(INDIGO_THERMOSTAT_DEVICE)
          http_response = do_authenticated_put(device_uri, {"setpointHeat" => degrees})
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Sprinklers
      listen_for /([a-z]*) sprinklers/i do |action|
        action.strip!
        begin
          if (action =~ /pause/i or action =~ /resume/i)
            device_uri = get_device_uri(INDIGO_SPRINKLER_DEVICE)
            http_response = do_authenticated_put(device_uri, {"activeZone" => action})
            say get_bot_response(http_response)
          else
            say "I don't understand that sprinkler action request"
          end
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Brightness
      listen_for /set(?: the)? ([a-z 0-9]*) brightness at ([0-9]*[0-9])(?: percent)?/i do |device_name, dim_value|
        device_name.strip!
        dim_value.strip!
        begin
          device_uri = get_device_uri(device_name)
          http_response = do_authenticated_put(device_uri, {"brightness" => dim_value})
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

      #Toggle
      listen_for /toggle(?: the)? ([a-z 1-9]*)/i do |device_name|
        device_name.strip!
        begin
          device_uri = get_device_uri(device_name)
          http_response = do_authenticated_put(device_uri, {"toggle" => "1"})
          say get_bot_response(http_response)
        rescue Exception=>e
          puts e.exception
        ensure
          request_completed
        end
      end

    end

Posted on
Wed May 22, 2013 2:52 pm
mreyn2005 offline
User avatar
Posts: 161
Joined: Oct 06, 2006

Re: SiriProxy plugin for Indigo

Hey Brian,
I remember getting a similar error during my development. I would guess that your match phrases contain a syntax error, but I can't recall exactly if that was the cause of my (similar) errors.

I just moved back to my house from being out of town for work for a year. Just started a new job as well. Been working on the yard for the last 2 weekends. I should be able to wrap up this weekend.

That being said, I should have some time in the near future to help you track this down, if you haven't figured it out by then.

I would start by taking out the optional portions of your match phrases. In general, simplify them as much as possible. Add bits back in one at a time until you find the culprit.

Cheers!
Matt

Posted on
Sat May 25, 2013 8:17 pm
dmeeker@mac.com offline
Posts: 85
Joined: Aug 26, 2011

Re: SiriProxy plugin for Indigo

jay (support) wrote:
Awesome - I haven't yet tried it but I may just have to when it's ready. What's the state of getting SiriProxy running? I looked at it quite a while back and it seemed pretty complicated. I'd love it if it were as easy as a double-click (or better yet an Indigo plugin).


Jay - I spent about 2 hours yesterday getting it running on a Raspberry Pi. I thought that would be a fun experiment, and I had one sitting around, and didn't feel like going through a hundred steps on the mac mini that runs indigo, so I went this route.

You can find a pre-configured SD card OS build, as well as good instructions for a bunch of different OS's.
https://github.com/plamoni/SiriProxy/wi ... on-How-Tos

It run fine, and took some finessing, but for the most part it was not that painful. Half the time was spent updating the SD card or installing this siriproxy plugin code to make it work. Siriproxy runs great and once I got the plugin installed, it hijacked my indigo violet commands, however it just hung. I came back to the forum to see what was up, and that is when I started reading about the authentication issues. I haven't tested yet with auth turned off, and I also haven't read all the posts yet.. I have some ideas though.

Anyway, if you haven't installed it yet, this was a pretty easy way to go and looks promising.

I agree with the earlier post about this type of control being ideal. I'm starting to work on a API connector for Google Glass and Indigo's Rest API, so I can build a "Glassware" app that would allow for full control from Glass, including control as well as camera access.

Posted on
Sat May 25, 2013 8:27 pm
dmeeker@mac.com offline
Posts: 85
Joined: Aug 26, 2011

Re: SiriProxy plugin for Indigo

mreyn2005 wrote:
It's that easy! Clear as mud? :-)

Matt



Matt - Is the latest code all in the git repo? Or are the changes you post ^above^ new? I installed from the git repo last night, and it fires the plugin, but the proxy just hangs there. I suspected it was the http auth, but you mention that was fixed.

To get the plugin running, I actually downloaded it to the sirirproxy server from git, into the /sirirproxy/plugins folder, then I rebundled everything and the server started to hijack any indigo commands (I could see it on the terminal I was ssh'd in on), but nothing seemed to happen. I assume it is the authentication, but perhaps you have another idea. Could it be the way I installed it (instead of just copying the code into the sample plugin?)

Who is online

Users browsing this forum: No registered users and 9 guests