-- Time Of Use: -- Limit HVAC and other switched device run times based on energy time-of-use -- schedules. Thermostats are switched to "Off" and limited from running -- depending on the current policy (Peak, holiday, etc). -- -- History -- 2015-08-19: Revision 4 - Added credits now switch HVAC back on -- 2015-08-06: Revision 3 - Credits to avoid switching "Off" if HVAC idle -- 2015-08-05: Revision 2 - Time-of-use table lookup -- 2015-07-25: Revision 1 - Initial algorithm, thermostat off<->previous mode local id_hvac1 = 39 local id_switch1 = 63 -------------------------------------------------------------------------------- -- TIME OF USE: PG&E E6 POLICY -- http://www.pge.com/tariffs/toudates.shtml -- http://www.pge.com/tariffs/ResTOUCurrent.xls -- 2015 Holidays - 1/1, 2/16, 5/25, 7/4, 9/7, 11/11, 11/26, 12/25 -------------------------------------------------------------------------------- local tou_ticks = 15 -- Ticks per cycle, must be <60 minutes local tou_fullpeak = {t="FullPeak", l=4, h=0, s=255} local tou_partpeak = {t="PartPeak", l=8, h=9000, s=255} local tou_cooldown = {t="CoolDown", l=10, h=25500, s=255} local tou_holiday = {t="Holiday", l=tou_ticks, h=14922, s=144} local tou_offpeak = {t="OffPeak", l=tou_ticks, h=14922, s=144} local tou_periods = { {toy="Summer", fmonth=5, lmonth=10, {tow="Weekday", fwday=2, lwday=6, {b=13, e=19, p=tou_fullpeak}, {b=10, e=21, p=tou_partpeak}, {b=21, e=24, p=tou_cooldown}}, {tow="Weekend", fwday=1, lwday=7, {b=17, e=20, p=tou_partpeak}, {b=20, e=23, p=tou_cooldown}}}, {toy="Winter", fmonth=1, lmonth=12, {tow="Weekday", fwday=2, lwday=6, {b=17, e=20, p=tou_partpeak}, {b=20, e=23, p=tou_cooldown}}}} local tou_holidays = { {m=1,d=1}, {m=2,d=16}, {m=5,d=25}, {m=7,d=4}, {m=9,d=7}, {m=11,d=11}, {m=11,d=26}, {m=12,d=25}} -------------------------------------------------------------------------------- -- tou_find_policy() - Based on the date/time walk through the time-of-use -- holidays and periods to find the first match. Return "Off Peak" if none. -------------------------------------------------------------------------------- function tou_find_policy(dt, holidays, periods) -- Holidays by month/date for ihol,hol in ipairs(holidays) do if (dt.month == hol.m) and (dt.day == hol.d) then return tou_holiday end end -- Periods by time-of-year/time-of-week/time-of-day for itoy,toy in ipairs(periods) do if (dt.month >= toy.fmonth) and (dt.month <= toy.lmonth) then for itow,tow in ipairs(toy) do if (dt.wday >= tow.fwday) and (dt.wday <= tow.lwday) then for itod,tod in ipairs(tow) do if (dt.hour >= tod.b) and (dt.hour < tod.e) then return tod.p end end end end end end return tou_offpeak end -------------------------------------------------------------------------------- -- tou_hvac_tick - Toggle thermostat modes and switches based on tick limit and -- available credits. Credits are earned for being idle while active, and lost -- while active or limited. When limiting the HVAC it is turned OFF and the -- "TimeOfUsePrevious" variable records the previous mode. When resuming set -- "ModeTarget", then next tick verify "ModeStatus" is correct before clearing -- "TimeOfUsePrevious" to NONE. This insures if the trainsmit fails we don't -- lose the operating mode, which could be an issue especially for Nest. -------------------------------------------------------------------------------- function tou_hvac_tick(tick, policy, dt, id) local urn_hvac_mode = "urn:upnp-org:serviceId:HVAC_UserOperatingMode1" local urn_hvac_state = "urn:micasaverde-com:serviceId:HVAC_OperatingState1" local mode_status = luup.variable_get(urn_hvac_mode, "ModeStatus", id) local mode_state = luup.variable_get(urn_hvac_state, "ModeState", id) -- TimeOfUseCredits: Increment if thermostat on but idle; decrement if active local var_credits = tonumber(luup.variable_get("TimeOfUse", "TimeOfUseCredits", id) or 0) if (mode_status ~= "Off") then if (mode_state == "Idle") and (var_credits < policy.l) then var_credits = var_credits + 1 elseif (mode_state ~= "Idle") and (var_credits > 0) then var_credits = var_credits - 1 end end -- TimeOfUsePrevious: Set "Off" if active or previous mode when limited local var_previous = luup.variable_get("TimeOfUse", "TimeOfUsePrevious", id) or "None" if (tick >= policy.l) and (var_credits == 0) then if (var_previous == "None") then luup.variable_set("TimeOfUse", "TimeOfUsePrevious", mode_status, id) luup.call_action(urn_hvac_mode, "SetModeTarget", {NewModeTarget = "Off"}, id) end elseif (var_previous ~= "None") then if (var_previous == mode_status) then luup.variable_set("TimeOfUse", "TimeOfUsePrevious", "None", id) end luup.call_action(urn_hvac_mode, "SetModeTarget", {NewModeTarget = var_previous}, id) end luup.variable_set("TimeOfUse", "TimeOfUseCredits", var_credits, id) luup.variable_set("TimeOfUse", "TimeOfUsePolicy", policy.t, id) end -------------------------------------------------------------------------------- -- tou_switch_tick() - Set the switch target based on the tick and policy. -------------------------------------------------------------------------------- function tou_switch_tick(tick, policy, id) local urn_switch = "urn:upnp-org:serviceId:SwitchPower1" local switch_target = 1 if (tick > policy.l) then switch_target = 0 end luup.call_action(urn_switch , "SetTarget", {newTargetValue = switch_target}, id) end -------------------------------------------------------------------------------- -- Process the current tick for HVAC, switches, etc. -------------------------------------------------------------------------------- local curr_time = os.date('*t') local curr_tick = ((curr_time.hour*60) + curr_time.min) % tou_ticks local curr_policy = tou_find_policy(curr_time, tou_holidays, tou_periods) -- HVAC tou_hvac_tick(curr_tick, curr_policy, curr_time, id_hvac1) -- SWITCH tou_switch_tick(curr_tick, curr_policy, id_switch1) return true