Skip to content
This repository has been archived by the owner on Feb 21, 2022. It is now read-only.

Commit

Permalink
Added support for hot water boost, reporting the status of away mode,…
Browse files Browse the repository at this point in the history
… and setting hold until a time rather than just a duration
  • Loading branch information
alex@thouky.co.uk committed Jul 21, 2013
1 parent d81feea commit c37b2cc
Showing 1 changed file with 100 additions and 12 deletions.
112 changes: 100 additions & 12 deletions siriproxy/lib/siriproxy-heatmiserwifi.rb
Expand Up @@ -23,6 +23,7 @@
require 'date'
require 'set'
require 'siri_objects'
require 'time'
require_relative 'thermostat'


Expand Down Expand Up @@ -50,14 +51,15 @@ def initialize(config)
RE_TARGET = /(?:target )?temperature|target/i
RE_OPTIONAL_TARGET = /(?:#{RE_TARGET} )?/
RE_HOLD = /hold(?:ing)?|keep|maintain/i
RE_BOOST = /boost|continue|extend|lengthen|prolong/i
RE_HOTCOLD = /cold|cool|hot|warm/i
RE_WHATIS = /(?:what (?:is|are)|what's|check|examine|interrogate|query)(?: the)?/i
RE_SWITCH = /switch|turn|place|put/i
RE_IS = /are|if|is/i
RE_ISIT = /is it/i
RE_HOW = /how/i
RE_FOR = /for/i
RE_OVERRIDE = /override|manual(?: control)?/i
RE_OVERRIDE = /boost|override|manual(?: control)?/i
RE_SET = /place|put|set|#{RE_SWITCH} on/i
RE_TO = /at|in|to|two/i # (Siri sometimes recognises 'to' as 'two')
RE_OPTIONAL_BY = /(?:by )?/i
Expand All @@ -67,7 +69,7 @@ def initialize(config)
RE_DECREASE = /decrease|reduce|lower/i
RE_CANCEL = /abort|cancel|countermand|end|finish|rescind|revoke|stop|terminate/i
RE_AWAY = /(?:(?:in|on|to) )?(?:holiday|away)(?: (?:mode|state))?/i
RE_RETURN =/until|return(?:ing)?(?: (?:on|at))?/i
RE_UNTIL = /until|return(?:ing)?(?: (?:on|at))?/i
RE_ON = /on/i
RE_OFF = /off/i
RE_ONOFF = /(#{RE_ON}|#{RE_OFF})/
Expand Down Expand Up @@ -121,9 +123,12 @@ def initialize(config)
RE_YEAR = /\d\d\d\d/
RE_DATE_UK = /#{RE_DAY} (?:of )?#{RE_MONTH}(?: #{RE_YEAR})?/i
RE_DATE_US = /#{RE_MONTH} #{RE_DAY}(?: #{RE_YEAR})?/i
RE_DATE = /#{RE_DATE_UK}|#{RE_DATE_US}/
RE_TIME = /\d\d:\d\d(?: ?(?:AM|PM))/i
RE_DATETIME = /(#{RE_DATE}|#{RE_TIME}|#{RE_TIME} (?:on )#{RE_DATE})/i
RE_DATE_PART = /#{RE_DATE_UK}|#{RE_DATE_US}/
RE_TIME_HOURS = /\d?\d ?(?:AM|PM|o'clock)/i
RE_TIME_FULL = /\d?\d:\d\d(?: ?(?:AM|PM))/i
RE_TIME_PART = /#{RE_TIME_HOURS}|#{RE_TIME_FULL}/
RE_TIME = /(#{RE_TIME_PART})/
RE_DATETIME = /(#{RE_DATE_PART}|#{RE_TIME_PART}|#{RE_TIME_PART} (?:on )#{RE_DATE_PART})/i

# Allow more readable regexps for speech patterns

Expand Down Expand Up @@ -156,14 +161,16 @@ def self.listen_for_phrase(pattern, &block)
listen_for_phrase('SWITCH ONOFF THERMOSTATS')\
{ |onoff, thermostats| action_onoff thermostats, onoff }

listen_for_phrase('SET THERMOSTATS AWAY RETURN DATETIME')\
listen_for_phrase('SET THERMOSTATS AWAY UNTIL DATETIME')\
{ |thermostats, datetime| action_holiday thermostats, datetime }

listen_for_phrase('CANCEL THERMOSTATS AWAY')\
{ |thermostats| action_holiday_cancel thermostats }
listen_for_phrase('CANCEL AWAY FOR THERMOSTATS')\
{ |thermostats| action_holiday_cancel thermostats }

# HERE - Add 'SET THERMOSTATS TO SUMMERWINTER MODE'

# Queries that apply to thermostats with heating control

listen_for_phrase('WHATIS INTERIOR TEMPERATURE')\
Expand All @@ -185,6 +192,8 @@ def self.listen_for_phrase(pattern, &block)

listen_for_phrase('HOLD [THERMOSTATS] TARGET FOR DURATION')\
{ |thermostats, duration| action_hold_temperature thermostats, duration }
listen_for_phrase('HOLD [THERMOSTATS] TARGET UNTIL TIME')\
{ |thermostats, time| action_hold_temperature thermostats, time }

listen_for_phrase('HOLD THERMOSTATS [TARGET] TO DEGREES FOR DURATION')\
{ |thermostats, degrees, duration| action_hold_temperature thermostats, duration, degrees }
Expand All @@ -194,6 +203,14 @@ def self.listen_for_phrase(pattern, &block)
{ |thermostats, degrees, duration| action_hold_temperature thermostats, duration, degrees }
listen_for_phrase('SET [THERMOSTATS] TARGET TO DEGREES [AND] HOLD FOR DURATION')\
{ |thermostats, degrees, duration| action_hold_temperature thermostats, duration, degrees }
listen_for_phrase('HOLD THERMOSTATS [TARGET] TO DEGREES UNTIL TIME')\
{ |thermostats, degrees, time| action_hold_temperature thermostats, time, degrees }
listen_for_phrase('HOLD [THERMOSTATS] TARGET TO DEGREES UNTIL TIME')\
{ |thermostats, degrees, time| action_hold_temperature thermostats, time, degrees }
listen_for_phrase('SET THERMOSTATS [TARGET] TO DEGREES [AND] HOLD UNTIL TIME')\
{ |thermostats, degrees, time| action_hold_temperature thermostats, time, degrees }
listen_for_phrase('SET [THERMOSTATS] TARGET TO DEGREES [AND] HOLD UNTIL TIME')\
{ |thermostats, degrees, time| action_hold_temperature thermostats, time, degrees }

listen_for_phrase('SET THERMOSTATS [TARGET] TO DEGREES')\
{ |thermostats, degrees| action_set_temperature thermostats, degrees }
Expand Down Expand Up @@ -238,13 +255,24 @@ def self.listen_for_phrase(pattern, &block)

# Actions that apply to thermostats with hot water control

# HERE - Add 'SET THERMOSTATS TO HOMEAWAY'

listen_for_phrase('SWITCH [THERMOSTATS] HOTWATER ONOFF')\
{ |thermostats, onoff| action_hotwater_onoff thermostats, onoff }
listen_for_phrase('SWITCH ONOFF [THERMOSTATS] HOTWATER')\
{ |onoff, thermostats| action_hotwater_onoff thermostats, onoff }
listen_for_phrase('ENABLEDISABLE [THERMOSTATS] HOTWATER')\
{ |onoff, thermostats| action_hotwater_onoff thermostats, onoff }

listen_for_phrase('BOOST [THERMOSTATS] HOTWATER FOR DURATION')\
{ |thermostats, duration| action_hotwater_boost thermostats, duration }
listen_for_phrase('BOOST THERMOSTATS FOR DURATION')\
{ |thermostats, duration| action_hotwater_boost thermostats, duration }
listen_for_phrase('BOOST [THERMOSTATS] HOTWATER UNTIL TIME')\
{ |thermostats, time| action_hotwater_boost thermostats, time }
listen_for_phrase('BOOST THERMOSTATS UNTIL TIME')\
{ |thermostats, time| action_hotwater_boost thermostats, time }

listen_for_phrase('CANCEL [THERMOSTATS] HOTWATER OVERRIDE')\
{ |thermostats| action_hotwater_timer thermostats }

Expand Down Expand Up @@ -442,9 +470,11 @@ def query_hotwater_status(thermostats)
def action_hotwater_onoff(thermostats, onoff)
hotwater_on = speech_to_on(onoff)
action_with_thermostats_controlling_hotwater(thermostats) do |ts|
action_precondition_active(ts, 'turning the hot water on') if hotwater_on
if hotwater_on
action_precondition_hotwater(ts, 'turning the hot water on')
end
ts.each do |t|
if hotwater_on or action_active?(t)
if hotwater_on or action_hotwater?(t)
if t.hotwater_active? == hotwater_on
add_response nil, t,
"hot water was already #{onoff_to_s(hotwater_on)}"
Expand All @@ -458,6 +488,17 @@ def action_hotwater_onoff(thermostats, onoff)
end
end

def action_hotwater_boost(thermostats, duration)
action_with_thermostats_controlling_hotwater(thermostats) do |ts|
action_precondition_hotwater(ts, 'turning the hot water on')
ts.each do |t|
minutes = speech_to_minutes(duration)
t.boost(minutes) # Set the hot water boost
add_response nil, t, *status_items_hotwater(t)
end
end
end

def action_hotwater_timer(thermostats)
action_each_thermostat_controlling_hotwater(thermostats) do |t|
if action_active?(t)
Expand Down Expand Up @@ -513,11 +554,17 @@ def status_items_heating(t)
end

def status_items_hotwater(t)
status = []
if t.on? and not t.holiday?
["hot water is #{onoff_to_s(t.hotwater_active?)}"]
else
[]
if not t.home_mode?
status << 'is in away mode'
end
status << "hot water is #{onoff_to_s(t.hotwater_active?)}"
if t.boost?
status.last << " being boosted for #{minutes_to_s(t.boost_minutes)}"
end
end
status
end

# Common operations in actions
Expand All @@ -532,6 +579,16 @@ def action_precondition_heating(ts, description) #, [yield]
end
end

def action_precondition_hotwater(ts, description) #, [yield]
action_precondition_active(ts, description) do |t|
if not t.home_mode?
add_response nil, t, 'is in away mode'
else
yield t if block_given?
end
end
end

def action_precondition_active(ts, description) #, [yield]
action_precondition(ts, description) do |t|
if not t.on?
Expand Down Expand Up @@ -564,6 +621,17 @@ def action_heating?(t)
return false
end

def action_hotwater?(t)
if action_active?(t)
if not t.home_mode?
add_response 'I have left', t, 'in away mode'
else
return true
end
end
return false
end

def action_active?(t)
if not t.on?
add_response 'I have left', t, 'switched off'
Expand Down Expand Up @@ -820,7 +888,18 @@ def temperature_f_to_c(fahrenheit, interval = false)
(fahrenheit - (interval ? 0 : 32)) * 5.0 / 9
end

def speech_to_minutes(duration)
def speech_to_minutes(duration_or_time)
case duration_or_time
when RE_DURATION
speech_duration_to_minutes(duration_or_time)
when RE_TIME
speech_time_to_minutes(duration_or_time)
else
raise "Unrecognised duration \"#{duration_or_time}\""
end
end

def speech_duration_to_minutes(duration)
number = speech_to_i duration
multiplier = case duration
when /#{RE_DURATION_UNITS_MINUTES}$/
Expand All @@ -835,6 +914,15 @@ def speech_to_minutes(duration)
number * multiplier
end

def speech_time_to_minutes(time)
time.sub!(/\s*o'clock$/i, ':00')
t_now = Time.now
t_end = Time.parse(time, t_now)
seconds = t_end - t_now
hours_range = time =~ /AM|PM/i || 12 <= t_end.hour ? 24 : 12
(seconds / 60) % (hours_range * 60)
end

def date_to_s(date)
date.strftime("%I:%M %p on %A #{ordinal_to_s(date.mday)} %B %Y")
end
Expand Down

0 comments on commit c37b2cc

Please sign in to comment.