interstateone/siriproxy-hue

View on GitHub
lib/siriproxy-hue.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# SiriProxy-Hue
# https://github.com/interstateone/siriproxy-hue
# copyright 2013, Brandon Evans
# extended version and bug fixes, Christoph Weil
# Released under MIT License

# SiriProxy Plugin Requirements
require "cora"
require "siri_objects"
require "pp"

require "rest_client" # HTTP requests
require "json" # Parse Hue responses
require "matrix" # Color space transformations
require "hue-entity"

class SiriProxy::Plugin::Hue < SiriProxy::Plugin
    def initialize (config)
        @@bridgeIP = config['bridge_ip']
        @@username = config['username']
        @@registering = false
        @@registered = false
    end
    
    def checkRegistration
        if @@registered then return end
        
        url = "#{@@bridgeIP}/api/#{@@username}"
        response = RestClient.get(url)
        data = JSON.parse(response)
        if data.kind_of?(Array)
            data = data[0]
        end
        
        if data.has_key? "error"
            # Unregistered username
            @@registering = true
            response = ask "I just need to finish setting up: say \"Ready\" after you've pressed the link utton on your Hue bridge."
            if response =~ /ready/i
                url = "#{@@bridgeIP}/api"
                data = {devicetype: "siriproxy", username: "#{@@username}"}.to_json
                response = RestClient.post(url, data, content_type: :json)
                request_completed
                checkRegistration
            end
            else
            # Success
            if @@registering
                @@registering = false
                say "Thanks, you're ready to go."
                request_completed
                @@registered = true
            else
                @@registered = true
            end
        end
    end
    
    def parseNumbers (value)
        if (value =~ /%/)
            value = value.delete("%").to_i * 254 / 100 # 254 is max brightness value
        end
    end
    
    def is_numeric?(obj)
        obj.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
    end
    
    def ensureMatchedEntity(entity)
        begin
            matchedEntity = HueEntity.new(entity, @@bridgeIP, @@username)
            rescue ArgumentError
            say "I couldn't find any lights by that name."
            request_completed
            return
        end
        matchedEntity
    end
    
    def switchEntity(state, matchedEntity)
        if (state == "on")
            matchedEntity.power(true)
        else
            matchedEntity.power(false)
        end
        
        if matchedEntity.type == :group
            if matchedEntity.name == "all"
                say "I've turned #{state} all of the lights for you."
            else
                say "I've turned #{state} the #{matchedEntity.name} lights for you."
            end
        else
            say "I've turned #{state} the #{matchedEntity.name} light for you."
        end
        request_completed
    end
    
    def setRelativeBrightness(change, matchedEntity)
        currentBrightness = matchedEntity.brightness
        
        if (change == "up")
            newBrightness = currentBrightness + 50
        else
            newBrightness = currentBrightness - 50
        end
        matchedEntity.brightness(newBrightness)
        
        response = ask "Is that enough?"
        
        if (response =~ /yes/i)
            say "I'm happy to help."
        elsif (response =~ /more|no/i)
            if (change == "up")
                newBrightness += 50
            else
                newBrightness -= 50
            end
            matchedEntity.brightness(newBrightness)
            say "You're right, that does look better"
        end
        
        request_completed
    end
    
    def setAbsoluteBrightness(value, matchedEntity)
        numericValue = parseNumbers(value)
        if (is_numeric? numericValue)
            log numericValue
            matchedEntity.brightness(numericValue.to_i)
        else
            # query colourlovers for first rgb value from the given string
            url = "http://www.colourlovers.com/api/colors?keywords=#{value}&numResults=1&format=json"
            url.gsub!(/\s/,'%20')
            response = RestClient.get(url)
            if response.eql?("[]") then
                say "I couldn't find any color by that name."
                request_completed
            else
                data_rgb = JSON.parse(response)[0]["rgb"]
                data_hsv = JSON.parse(response)[0]["hsv"]
                matchedEntity.color(data_rgb["red"], data_rgb["green"], data_rgb["blue"], data_hsv["saturation"])
            end     
        end
        say "There you go."
        request_completed
    end
    # Binary state
    listen_for %r/turn (on|off)(?: the)? ([a-z ]??*)(?: the)? light(?:s)?/i do |state, entity|
        checkRegistration
        matchedEntity = ensureMatchedEntity(entity)
        switchEntity(state, matchedEntity)
    end

    # Relative brightness
    listen_for %r/turn (up|down)(?: the)? ([a-z ]??*)(?: the)? light(?:s)?/i do |change, entity|
        checkRegistration
        matchedEntity = ensureMatchedEntity(entity)
        setRelativeBrightness(change, matchedEntity)
    end

    # Absolute brightness/color change
    #   Numbers (0-254) and percentages (0-100) are treated as brightness values
    #   Single words are used as a color query to lookup HSV values
    listen_for %r/set(?: the)? ([a-z ]??*)(?: the)? light(?:s)? to ([a-z0-9% ]*)/i do |entity, value|
        checkRegistration
        matchedEntity = ensureMatchedEntity(entity)
        setAbsoluteBrightness(value, matchedEntity)
    end

    # TODO Scenes
    listen_for %r/make it look like a (.+)/i do |scene|
        checkRegistration
        # pull n colors, where n is the number of lights
        # set each light to color[i]
        request_completed
    end
end