From 3909d6a9f7e04e5bec67cf55ee7546a9873bdd76 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Wed, 31 Aug 2016 19:10:28 -0400
Subject: [PATCH 01/49] Initial implementation for dimmer value and scene
support
---
I_DeusExMachinaII1.xml | 197 ++++++++++++++++++------
J_DeusExMachinaII1_UI7.js | 316 +++++++++++++++++++++++++++++++-------
2 files changed, 415 insertions(+), 98 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index fc55c05..f9fada2 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -13,7 +13,7 @@
SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
- DEMVERSION = 20300
+ DEMVERSION = 20302
STATE_STANDBY = 0
STATE_IDLE = 1
@@ -62,19 +62,23 @@
end
-- DEM cycles lights between sunset and the user-specified off time. This function returns 0
- -- if the current time is between sunset and off; otherwise 1.
+ -- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
+ -- to minutes-since-midnight units.
local function isBedtime()
local testing = getVarNumeric("TestMode", 0)
if (testing ~= 0) then luup.log('DeusExMachinaII::isBedtime(): TestMode is on') end
- if (not (testing or luup.is_night())) then
- return 1
- end
+
+ -- Establish the lights-out time
local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
- local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
+ local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
if (bedtime_tmp ~= nil) then
bedtime_tmp = tonumber(bedtime_tmp,10)
if (bedtime_tmp >= 0 and bedtime_tmp < 1440) then bedtime = bedtime_tmp end
end
+
+ -- Figure out our sunset time. Note that if we make this inquiry after sunset, MiOS
+ -- returns the time of tomorrow's sunset. But, that's not different enough from today's
+ -- that it really matters to us, so go with it.
local date = os.date('*t', luup.sunset())
local sunset = date['hour'] * 60 + date['min']
if (testing ~= 0) then
@@ -82,12 +86,16 @@
if (s ~= nil) then sunset = s end
luup.log('DeusExMachinaII::isBedtime(): testing mode sunset override time is ' .. tostring(sunset))
end
+
+ -- And the current time.
date = os.date('*t')
local time = date['hour'] * 60 + date['min']
+
+ -- Figure out if we're betweeen sunset and lightout (ret=0) or not (ret=1)
if (testing ~= 0) then
luup.log('DeusExMachinaII:isBedtime(): times (mins since midnight) are now=' .. tostring(time) .. ', sunset=' .. tostring(sunset) .. ', bedtime=' .. tostring(bedtime))
end
- local ret = 1;
+ local ret = 1 -- guilty until proven innocent
if (bedtime > sunset) then
-- Case 1: bedtime is after sunset (i.e. between sunset and midnight)
if (time >= sunset and time < bedtime) then
@@ -103,14 +111,17 @@
return ret
end
- -- Get the list of controlled devices from our device state, parse to table of device IDs.
- local function getDeviceList()
- local s = luup.variable_get(SID, "Devices", lul_device) or ""
+ -- Take a string and split it around sep, returning table (indexed) of substrings
+ -- For example abc,def,ghi becomes t[1]=abc, t[2]=def, t[3]=ghi
+ -- Returns: table of values, count of values (integer >= 0)
+ local function split(s, sep)
local t = {}
- local k = 1
local n = 0
+ if (#s == 0) then return t,n end -- empty string returns nothing
+ local i,j
+ local k = 1
repeat
- local i = string.find(s, ',', k)
+ i, j = string.find(s, sep or "%s*,%s*", k)
if (i == nil) then
table.insert(t, string.sub(s, k, -1))
n = n + 1
@@ -118,36 +129,134 @@
else
table.insert(t, string.sub(s, k, i-1))
n = n + 1
- k = i + 1
+ k = j + 1
end
until k > string.len(s)
return t, n
end
+ -- Return true if a specified scene has been run (i.e. on the list)
+ local function isSceneOn(spec)
+ local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ for i in string.gfind(stateList, "[^,]+") do
+ if (i == spec) then return true end
+ end
+ return false
+ end
+
+ -- Mark or unmark a scene as having been run
+ local function updateSceneState(spec, isOn)
+ local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ local i
+ local t = {}
+ for i in string.gfind(stateList, "[^,]+") do
+ t[i] = 1
+ end
+ if (isOn) then
+ t[spec] = 1
+ else
+ t[spec] = nil
+ end
+ stateList = ""
+ for i in pairs(t) do stateList = stateList .. "," .. tostring(i) end
+ luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
+ end
+
+ -- Run "final" scene, if defined. This scene is run after all other devices have been
+ -- turned off.
+ local function runFinalScene()
+ local scene = getVarNumeric("FinalScene", nil)
+ if (scene ~= nil) then
+ luup.log("DeusExMachina::runFinalScene(): running final scene " .. tostring(scene))
+ luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=scene }, 0)
+ end
+ end
+
+ -- Get the list of controlled devices from our device state, parse to table of device IDs.
+ local function getDeviceList()
+ local s = luup.variable_get(SID, "Devices", lul_device) or ""
+ return split(s)
+ end
+
-- Light on or off? Returns boolean
local function isDeviceOn(devid)
- local t = luup.devices[devid].device_type
- local val = 0
- if (t == SWITCH_TYPE) then
- val = luup.variable_get(SWITCH_SID, "Status", devid)
- elseif (t == DIMMER_TYPE) then
- val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", devid)
+ local first = string.upper(string.sub(devid, 1, 1))
+ if (first == "S") then
+luup.log("DEMII::isDeviceOn(): handling scene spec " .. devid)
+ return isSceneOn(devid)
+ end
+
+ -- Handle as switch or dimmer
+luup.log("DEMII::isDeviceOn(): handling device spec " .. devid)
+ local r = tonumber(string.match(devid, '%d+'), 10)
+luup.log("DEMII::isDeviceOn(): device number seems to be " .. tostring(r))
+ local val = "0"
+ if (luup.devices[r] ~= nil) then
+ local t = luup.devices[r].device_type
+ if (t == SWITCH_TYPE) then
+ val = luup.variable_get(SWITCH_SID, "Status", r)
+ elseif (t == DIMMER_TYPE) then
+ val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
+ else
+ luup.log("DeusExMachinaII::isDeviceOn(): device " .. tostring(devid) .. " unknown device_type " .. tostring(t))
+ return false
+ end
else
- luup.log("DeusExMachinaII::isDeviceOn(): device " .. tostring(devid) .. " unknown device_type " .. tostring(t))
+ luup.log("DeusExMachinaII::isDeviceOn(): device spec " .. tostring(devid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
+ return false
end
return val ~= "0"
end
- -- Control device
+ -- Control device. Device is a string, expected to be a pure integer (in which case the device is assumed to be a switch or dimmer),
+ -- or a string in the form Sxx:yy, in which case xx is an "on" scene to run, and yy is an "off" scene to run.
local function deviceControl(devid, turnOn)
- local t = luup.devices[devid].device_type
- local lvl = 0
- if (t == DIMMER_TYPE) then
- if (turnOn) then lvl = 100 end
- luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, devid) -- note odd case inconsistency
- elseif (t == SWITCH_TYPE) then
- if (turnOn) then lvl = 1 end
- luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, devid)
+ luup.log("DeusExMachinaII::deviceControl(): devid=" .. tostring(devid) .. ", turnOn=" .. tostring(turnOn))
+ local first = string.upper(string.sub(devid, 1, 1))
+ if first == "S" then
+luup.log("DEMII::deviceControl(): handling scene spec " .. devid)
+ i, j, onScene, offScene = string.find(string.sub(devid, 2), "(%d+)-(%d+)")
+ if (i == nil) then
+ luup.log("DeusExMachina::deviceControl(): malformed scene spec=" .. devid)
+ return
+ end
+ onScene = tonumber(onScene, 10)
+ offScene = tonumber(offScene, 10)
+ if luup.scenes[onScene] == nil or luup.scenes[offScene] == nil then
+ -- Both on scene and off scene must exist (defensive).
+ luup.log("DeusExMachinaII::deviceControl(): one or both of the scenes in " .. tostring(devid) .. " not found in luup.scenes!")
+ return
+ end
+luup.log("DEMII::deviceControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
+ local targetScene
+ if (turnOn) then targetScene = onScene else targetScene = offScene end
+ luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
+ updateSceneState(devid, turnOn)
+ else
+ local lvl = 100
+ local k = string.find(devid, '=')
+ if k ~= nil then
+ _, _, devid, lvl = string.find(devid, "(%d+)=(%d+)")
+ lvl = tonumber(lvl, 10)
+ end
+ devid = tonumber(devid, 10)
+luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", level " .. tostring(lvl))
+ if luup.devices[devid] == nil then
+ luup.log("DeusExMachinaII::deviceControl(): device " .. tostring(devid) .. " not found in luup.devices");
+ return
+ end
+ local t = luup.devices[devid].device_type
+ if not turnOn then
+ lvl = 0
+ end
+ if (t == DIMMER_TYPE) then
+ luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, devid) -- note odd case inconsistency
+ elseif (t == SWITCH_TYPE) then
+ if turnOn then lvl = 1 end
+ luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, devid)
+ else
+ luup.log("DeusExMachinaII: deviceControl(): can't determine device type of devspec=" .. devid)
+ end
end
end
@@ -159,17 +268,16 @@
local i
local on = {}
local n = 0
- for i = 1, max, 1 do
- local deviceId = tonumber(devs[i],10)
- if (isDeviceOn(deviceId)) then
- table.insert(on, deviceId)
+ for i = 1,max do
+ if (isDeviceOn(devs[i])) then
+ table.insert(on, devs[i])
n = n + 1
end
end
if (n > 0) then
i = math.random(1, n)
deviceControl(on[i], false)
- luup.log("DeusExMachinaII::turnOffLight(): set " .. on[i] .. " to OFF; " .. tostring(n-1) .. " devices still on.")
+ luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(on[i]) .. " OFF, " .. tostring(n-1) .. " devices still on.")
if (n > 1) then
return 1
end
@@ -259,6 +367,7 @@
-- Enable DEM by setting a new cycle stamp and calling an initial cycle directly.
function deusEnable()
luup.log("DeusExMachinaII::deusEnable(): enabling...")
+ luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
runStamp = os.time()
deusStep(runStamp)
end
@@ -272,7 +381,7 @@
local devs, count
devs, count = getDeviceList()
while count > 0 do
- deviceControl(tonumber(devs[count],10), false)
+ deviceControl(devs[count], false)
count = count - 1
end
end
@@ -370,7 +479,7 @@
local t = tonumber(s, 10)
local hh = math.floor(t / 60)
local mm = math.mod(t, 60)
- t = os.date('*t');
+ t = os.date('*t')
t['hour'] = hh
t['min'] = mm
t['sec'] = 0
@@ -388,6 +497,7 @@
maxdelay = getVarNumeric("MaxOffDelay", 300)
if (turnOffLight() == 0) then
-- No more lights to turn off, arm for next sunset
+ runFinalScene()
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
local delay = sunset - os.time() + math.random(mindelay,maxdelay)
luup.log("DeusExMachina::deusStep(): all lights out; waiting for next sunset in " .. delay)
@@ -403,15 +513,14 @@
devs, max = getDeviceList()
if (max > 0) then
local change = math.random(1, max)
- local deviceId = tonumber(devs[change],10)
- if (deviceId ~= nil) then
- local status = luup.variable_get(SWITCH_SID, "Status", deviceId)
- if (isDeviceOn(deviceId)) then
- deviceControl(deviceId, false)
- luup.log("DeusExMachinaII::deusStep(): set " .. deviceId .. " to OFF")
+ local devspec = devs[change]
+ if (devspec ~= nil) then
+ if (isDeviceOn(devspec)) then
+ deviceControl(devspec, false)
+ luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to OFF")
else
- deviceControl(deviceId, true)
- luup.log("DeusExMachinaII::deusStep(): set " .. deviceId .. " to ON")
+ deviceControl(devspec, true)
+ luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to ON")
end
end
else
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index 487d57a..f2c171a 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -8,7 +8,8 @@ var DeusExMachinaII = (function(api) {
var myModule = {};
var deusDevice = api.getCpanelDeviceId();
- var controlled;
+ var controlled = [];
+ var sceneNamesById = [];
function onBeforeCpanelClose(args) {
console.log('handler for before cpanel close');
@@ -18,6 +19,12 @@ var DeusExMachinaII = (function(api) {
api.registerEventHandler('on_ui_cpanel_before_close', myModule, 'onBeforeCpanelClose');
}
+ function safe(obj) {
+ if (obj === undefined) return "undefined"
+ else if (obj instanceof jQuery || obj.constructor.prototype.jquery) return "jQuery[" + obj.length + "]";
+ else return '(' + typeof(obj) + ')' + obj.toString();
+ }
+
function isLight(device) {
switch(device.device_type) {
case "urn:schemas-upnp-org:device:BinaryLight:1":
@@ -29,15 +36,85 @@ var DeusExMachinaII = (function(api) {
}
}
- function getControlled() {
+ function getControlledList() {
var list = get_device_state(deusDevice, serviceId, "Devices", 0);
if (typeof(list) == "undefined" || list.match(/^\s*$/)) {
return [];
}
- var res = list.split(',');
- return res;
+ return list.split(',');
}
-
+
+ function updateControlledList() {
+ controlled = [];
+ jQuery('input.controlled-device:checked').each( function( ix, obj ) {
+ var devid = jQuery(obj).attr('id').substr(6);
+ var level = 100;
+ var ds = jQuery('div#slider' + devid);
+ if (ds.length == 1)
+ level = ds.slider('option','value');
+ if (level < 100)
+ devid += '=' + level;
+ controlled.push(devid);
+ });
+ jQuery('.controlled-scenes').each( function( ix, obj ) {
+ var devid = jQuery(obj).attr('id');
+ console.log('updateControlledList: handling scene pair ' + devid);
+ controlled.push(devid);
+ });
+
+ var s = controlled.join(',');
+ console.log('Updating controlled list to ' + s);
+ api.setDeviceStatePersistent(deusDevice, serviceId, "Devices", s, 0);
+ }
+
+ // Find a controlled device in the Devices list
+ function findControlledDevice(deviceId)
+ {
+ for (var k=0; k= controlled.length) return undefined;
+ var ret = {};
+ var c = controlled[ix];
+ ret.index = ix;
+ ret.raw = c;
+ if (c.charAt(0) == 'S') {
+ ret.type = "scene";
+ var l = c.indexOf('-');
+ ret.onScene = c.substr(1,l-1); // portion after S and before -
+ ret.offScene = c.substr(l+1); // after -
+ } else {
+ ret.type = "device";
+ var l = c.indexOf('=');
+ var d,v
+ if (l < 0) {
+ d = c;
+ v = 100;
+ } else {
+ d = c.substr(0,l);
+ v = c.substr(l+1);
+ }
+ ret.device = d;
+ ret.value = v;
+ }
+ return ret;
+ }
+
+ function findControlledSceneSpec(sceneSpec)
+ {
+ return jQuery.inArray(sceneSpec, controlled);
+ }
+
function timeMinsToStr(totalMinutes)
{
var hours = Math.floor(totalMinutes / 60);
@@ -55,18 +132,83 @@ var DeusExMachinaII = (function(api) {
{
api.setDeviceStatePersistent(deusDevice, serviceId, "LightsOut", timeMins, 0);
}
-
+
+ function saveFinalScene(uiObj)
+ {
+ var scene = "";
+ if (uiObj.selectedIndex > 0)
+ scene = uiObj.options[uiObj.selectedIndex].value;
+ api.setDeviceStatePersistent(deusDevice, serviceId, "FinalScene", scene, 0);
+ }
+
+ function clean(name, dflt)
+ {
+ if (dflt === undefined) dflt = '(undefined)';
+ if (name === undefined) name = dflt;
+ return name;
+ }
+
+ function getScenePairDisplay(onScene, offScene)
+ {
+ var html = "";
+ var divid = 'S' + onScene.toString() + '-' + offScene.toString();
+ html += '';
+ html += 'remove_circle_outline ';
+ html += ' On: ' + clean(sceneNamesById[onScene], '(missing scene)') + '; Off: ' + clean(sceneNamesById[offScene], '(missing scene)');
+ html += ' '; // controlled
+ return html;
+ }
+
+ function addScenePair(onScene, offScene)
+ {
+ var sceneSpec = 'S' + onScene.toString() + '-' + offScene.toString();
+ var index = findControlledSceneSpec(sceneSpec);
+ if (index < 0) {
+ var html = getScenePairDisplay(onScene, offScene);
+ jQuery('ul#scenepairs').append(html);
+ updateControlledList();
+
+ jQuery("select#addonscene").prop("selectedIndex", 0);
+ jQuery("select#addoffscene").prop("selectedIndex", 0);
+ }
+ }
+
+ function removeScenePair(spec)
+ {
+ var index = findControlledSceneSpec(spec);
+ if (index >= 0) {
+ jQuery('li#' + spec).remove();
+ updateControlledList();
+ }
+ }
+
function updateDeusControl(deviceId)
{
- var index = jQuery.inArray(deviceId.toString(), controlled); // PHR 03
+ var index = findControlledDevice(deviceId);
+ // console.log('checkbox ' + deviceId + ' in controlled at ' + index);
if (index >= 0) {
- controlled.splice(index, 1);
-
+ // Remove device
+ jQuery("input#device" + deviceId).prop("checked", false);
+ jQuery("div#slider" + deviceId).slider("option", "disabled", true);
+ jQuery("div#slider" + deviceId).slider("option", "value", 1);
} else {
- controlled.push(deviceId);
+ // Add device
+ jQuery("input#device" + deviceId).prop("checked", true);
+ jQuery("div#slider" + deviceId).slider("option", "disabled", false);
+ jQuery("div#slider" + deviceId).slider("option", "value", 100);
+ }
+ updateControlledList();
+ }
+
+ function changeDimmerSlider( obj, val )
+ {
+ // console.log('changeDimmerSlider(' + obj.attr('id') + ', ' + val + ')');
+ var deviceId = obj.attr('id').substr(6);
+ var ix = findControlledDevice(deviceId);
+ if (ix >= 0) {
+ controlled[ix] = deviceId + (val < 100 ? '=' + val : ""); // 100% is assumed if not specified
+ updateControlledList();
}
-
- api.setDeviceStatePersistent(deusDevice, serviceId, "Devices", controlled.join(','), 0); // PHR 01
}
function changeHouseModeSelector( eventObject )
@@ -101,16 +243,30 @@ var DeusExMachinaII = (function(api) {
init();
var i, j, roomObj, roomid, html = "";
- html += "Enter the time (after sunset) to begin shutting off lights: ";
- html += " (HH:MM)";
- html += "";
- html += "When enabled, run only in these House Modes (if all unchecked, runs in any mode): ";
+ html += '';
+ html += ' ';
+ html += '';
+ html += "
Lights-Out Time Lights will cycle between sunset and the \"lights-out\" time. Enter the time to begin shutting off lights: ";
+ html += " (HH:MM)";
+
+ html += "House Modes ";
+ html += "When enabled, lights cycle only in these House Modes (if all unchecked, runs in any mode): ";
html += ' Home';
html += ' Away';
html += ' Night';
html += ' Vacation';
- html += "
";
var devices = api.getListOfDevices();
var rooms = [];
@@ -139,45 +295,112 @@ var DeusExMachinaII = (function(api) {
}
);
- html += "
Select the devices to be controlled when enabled: ";
- controlled = getControlled();
+ html += "
Controlled Devices Select the devices to be controlled: ";
+ controlled = getControlledList();
for (j=0; j
";
+ html += '
' + roomObj.name + " ";
for (i=0; i
= 0) { // PHR 03
- html += " checked=\"true\"";
- }
+ html += ' = 0)
+ html += ' checked="true"';
html += " onChange=\"DeusExMachinaII.updateDeusControl('" + roomObj.devices[i].id + "')\"";
html += " /> ";
html += "#" + roomObj.devices[i].id + " ";
html += roomObj.devices[i].name;
+ if (roomObj.devices[i].device_type == "urn:schemas-upnp-org:device:DimmableLight:1") html += '
';
html += " \n";
}
}
- html += " ";
-
- // Finish up
+ html += ""; // devs
+ // Handle scene pairs
+ html += 'Scene Control ';
+ html += 'In addition to controlling individual devices, DeusExMachinaII can also run scenes. Scenes are specified in pairs: a scene to do something (the "on" scene), and a scene to undo it (the "off" scene). To add a scene pair, select an "on" scene and an "off" scene and click the green plus. To remove a configured scene pair, click the red minus next to it.';
+ html += '
Add Scene Pair: On Scene:--choose-- ';
+ html += ' Off Scene:--choose-- ';
+ html += ' ';
+ html += ' add_circle_outline '
+ html += '';
+ html += ' ';
+
+ // Final scene
+ html += '
Final Scene The final scene, if specified, is run after all other devices have been turned off during a lights-out cycle.Final Scene: (none) ';
+ html += ' ';
+
+ html += 'More Information If you need more information about configuring DeusExMachinaII, please see the README in our GitHub repository .';
+
+ // Push generated HTML to page
api.setCpanelContent(html);
// Restore time field
var time = "23:59";
var timeMins = parseInt(api.getDeviceState(deusDevice, serviceId, "LightsOut"));
if (!isNaN(timeMins))
- {
time = timeMinsToStr(timeMins);
- }
jQuery("#deusExTime").val(time);
// Restore house modes
var houseModes = parseInt(api.getDeviceState(deusDevice, serviceId, "HouseModes"));
for (var k=1; k<=4; ++k) {
- if (houseModes & (1<= 0) {
+ var info = DeusExMachinaII.getControlled(ix);
+console.log('found for ' + info.type + ' ' + info.device + ' with value=' + info.value + ', raw=' + info.raw + ", restoring slider value...");
+ jQuery(obj).slider("option", "value", info.type == "device" ? info.value : 100);
+ }
+ });
+ });
+
+ // Load sdata to get scene list. Populate menus, load controlled scene pairs, final scene.
+ jQuery.ajax({
+ url: api.getDataRequestURL(),
+ data: { 'id' : 'sdata' },
+ dataType: 'json',
+ success: function( data, status ) {
+ var menu = "";
+ /* global */ sceneNamesById = [];
+ jQuery.each( data.scenes, function( ix, obj ) {
+ menu += '' + obj.name + ' ';
+ sceneNamesById[obj.id] = obj.name;
+ });
+ jQuery('select#addonscene').append(menu);
+ jQuery('select#addoffscene').append(menu);
+ validateScene();
+
+ for (var k=0; k
Date: Sun, 25 Sep 2016 13:20:22 -0400
Subject: [PATCH 02/49] List any device that would respond to SwitchPower1 or
Dimming1 basics. This should give us reach to control Philips Hue, virtual
switches, and other devices that can be connected. It would be nice if the
API provided a call to return the service list/tree, but alas, we have to
probe various status various that would consistent with those services being
present. This leads to some oddities in the list, like some motion sensors.
We may need to deal with that later.
---
J_DeusExMachinaII1_UI7.js | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index f2c171a..63b5eaf 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -25,15 +25,17 @@ var DeusExMachinaII = (function(api) {
else return '(' + typeof(obj) + ')' + obj.toString();
}
- function isLight(device) {
- switch(device.device_type) {
- case "urn:schemas-upnp-org:device:BinaryLight:1":
- case "urn:schemas-upnp-org:device:DimmableLight:1":
- return true;
-
- default:
- return false;
- }
+ function isDimmer(devid) {
+ var v = api.getDeviceState( devid, "urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus" );
+ if (v === undefined || v === false) return false;
+ return true;
+ }
+
+ function isControllable(devid) {
+ if (isDimmer(devid)) return true; /* a dimmer is a light */
+ var v = api.getDeviceState( devid, "urn:upnp-org:serviceId:SwitchPower1", "Status" );
+ if (v === undefined || v === false) return false;
+ return true;
}
function getControlledList() {
@@ -273,7 +275,7 @@ var DeusExMachinaII = (function(api) {
var noroom = { "id": "0", "name": "No Room", "devices": [] };
rooms[noroom.id] = noroom;
for (i=0; i ";
html += "#" + roomObj.devices[i].id + " ";
html += roomObj.devices[i].name;
- if (roomObj.devices[i].device_type == "urn:schemas-upnp-org:device:DimmableLight:1") html += '
';
+ if (isDimmer(roomObj.devices[i].id)) html += '
';
html += " \n";
}
}
From b956811ee3865ad185042c402e4c50c42205e807 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 25 Sep 2016 13:25:33 -0400
Subject: [PATCH 03/49] Interim development check-in. Support for more devices
by probing service variables (why can't I find a list or tree of services the
device supports?). Improve handling of house mode transitions during our
active period. We now turn off all lights immediately, as we would if we were
disabled, and then wait for another house mode transition (pretty much by
polling) throughout the active time period in case it changes back to an
active state for us.
---
I_DeusExMachinaII1.xml | 390 ++++++++++++++++++++++++-----------------
1 file changed, 226 insertions(+), 164 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index f9fada2..7f0dee7 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -7,21 +7,21 @@
-- Original code and releases 1.x by Andy Lintner (beowulfe) Version 2.0 and beyond by Patrick Rigney (rigpapa/toggledbits).
-- A big thanks to Andy for passing the torch so that this great plug-in can live on.
-- -------------------------------------------------------------------------------------------------------------------------
-
+
SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
SWITCH_TYPE = "urn:schemas-upnp-org:device:BinaryLight:1"
SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
DEMVERSION = 20302
-
+
STATE_STANDBY = 0
STATE_IDLE = 1
STATE_CYCLE = 2
STATE_SHUTDOWN = 3
-
+
runStamp = 0
-
+
local function checkVersion()
local ui7Check = luup.variable_get(SID, "UI7Check", lul_device) or ""
if ui7Check == "" then
@@ -34,7 +34,7 @@
luup.reload()
end
end
-
+
-- Get numeric variable, or return default value if not set or blank
local function getVarNumeric( name, dflt )
local s = luup.variable_get(SID, name, lul_device)
@@ -43,11 +43,11 @@
if (s == nil) then return dflt end
return s
end
-
+
-- Delete a variable (if we can... read on...)
local function deleteVar( name, devid )
if (devid == nil) then devid = luup.device end
- -- Interestingly, setting a variable to nil with luup.variable_set does nothing interesting; too bad, it
+ -- Interestingly, setting a variable to nil with luup.variable_set does nothing interesting; too bad, it
-- could have been used to delete variables, since a later get would yield nil anyway. But it turns out
-- that using the variableget Luup request with no value WILL delete the variable.
local req = "http://127.0.0.1:3480/data_request?id=variableset&DeviceNum=" .. tostring(devid) .. "&serviceId=" .. SID .. "&Variable=" .. name .. "&Value="
@@ -55,12 +55,80 @@
local status, result = luup.inet.wget(req)
-- luup.log("DeusExMachinaII::deleteVar(" .. name .. "): status=" .. tostring(status) .. ", result=" .. tostring(result))
end
-
+
-- Shortcut function to return state of Enabled variable
- local function isEnabled()
- return getVarNumeric("Enabled", 0)
+ local function isEnabled()
+ return ( getVarNumeric("Enabled", 0) ~= 0 )
+ end
+
+ local function isActiveHouseMode()
+ -- Fetch our mask bits that tell us what modes we operate in. If 0, we're not checking house mode.
+ local modebits = getVarNumeric("HouseModes", 0)
+ if (modebits ~= 0) then
+ -- Get the current house mode. There seems to be some disharmony in the correct way to go
+ -- about this, but the method (uncommented) below works.
+ -- local currentMode = luup.attr_get("Mode") -- alternate method
+ local currentMode
+ local status
+ status, currentMode = luup.inet.wget("http://127.0.0.1:3480/data_request?id=variableget&Variable=Mode", 2)
+ if status ~= 0 then
+ luup.log("DeusExMachinaII::isActiveHouseMode(): can't get current house mode, status=" .. tostring(status))
+ currentMode = 0
+ end
+
+ -- Check to see if house mode bits are non-zero, and if so, apply current mode as mask.
+ -- If bit is set (current mode is in the bitset), we can run, otherwise skip.
+ local bit = require("bit")
+ -- Get the current house mode (1=Home,2=Away,3=Night,4=Vacation)
+ currentMode = math.pow(2, tonumber(currentMode,10))
+ if (bit.band(modebits, currentMode) == 0) then
+ luup.log('DeusExMachinaII::isActiveHouseMode(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' not in set ' .. string.format("0x%x", modebits))
+ return false -- not active in this mode
+ end
+ end
+ return true -- default is we're active in the current house mode
+ end
+
+ -- Get a random delay from two state variables. Error check.
+ function getRandomDelay(minStateName,maxStateName,defMin,defMax)
+ if defMin == nil then defMin = 300 end
+ if defMax == nil then defMax = 1800 end
+ local mind = getVarNumeric(minStateName, defMin)
+ if mind < 1 then mind = 1 elseif mind > 7200 then mind = 7200 end
+ local maxd = getVarNumeric(maxStateName, defMax)
+ if maxd < 1 then maxd = 1 elseif maxd > 7200 then maxd = 7200 end
+ if maxd < mind then maxd = mind end
+ return math.random( mind, maxd )
+ end
+
+ -- Get sunset time in minutes since midnight. May override for test mode value.
+ function getSunset()
+ -- Figure out our sunset time. Note that if we make this inquiry after sunset, MiOS
+ -- returns the time of tomorrow's sunset. But, that's not different enough from today's
+ -- that it really matters to us, so go with it.
+ local sunset = luup.sunset()
+ local testing = getVarNumeric("TestMode", 0)
+ if (testing ~= 0) then
+ local m = getVarNumeric( "TestSunset", nil ) -- units are minutes since midnight
+ if (m ~= nil) then
+ -- Sub in our test sunset time
+ local t = os.date('*t', sunset)
+ t['hour'] = math.floor(m / 60)
+ t['min'] = math.floor(m % 60)
+ t['sec'] = 0
+ sunset = os.time(t)
+ end
+ luup.log('DeusExMachinaII::getSunset(): testing mode sunset override ' .. tostring(m) .. ', as time is ' .. tostring(sunset))
+ end
+ if (sunset <= os.time()) then sunset = sunset + 86400 end
+ return sunset
end
-
+
+ function getSunsetMSM()
+ local t = os.date('*t', getSunset());
+ return t['hour']*60 + t['min']
+ end
+
-- DEM cycles lights between sunset and the user-specified off time. This function returns 0
-- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
-- to minutes-since-midnight units.
@@ -70,30 +138,22 @@
-- Establish the lights-out time
local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
- local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
+ local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
if (bedtime_tmp ~= nil) then
bedtime_tmp = tonumber(bedtime_tmp,10)
if (bedtime_tmp >= 0 and bedtime_tmp < 1440) then bedtime = bedtime_tmp end
end
-
- -- Figure out our sunset time. Note that if we make this inquiry after sunset, MiOS
- -- returns the time of tomorrow's sunset. But, that's not different enough from today's
- -- that it really matters to us, so go with it.
- local date = os.date('*t', luup.sunset())
- local sunset = date['hour'] * 60 + date['min']
- if (testing ~= 0) then
- local s = getVarNumeric( "TestSunset", nil )
- if (s ~= nil) then sunset = s end
- luup.log('DeusExMachinaII::isBedtime(): testing mode sunset override time is ' .. tostring(sunset))
- end
-
+
+ -- Figure out our sunset time.
+ local sunset = getSunsetMSM()
+
-- And the current time.
- date = os.date('*t')
+ local date = os.date('*t')
local time = date['hour'] * 60 + date['min']
-
+
-- Figure out if we're betweeen sunset and lightout (ret=0) or not (ret=1)
- if (testing ~= 0) then
- luup.log('DeusExMachinaII:isBedtime(): times (mins since midnight) are now=' .. tostring(time) .. ', sunset=' .. tostring(sunset) .. ', bedtime=' .. tostring(bedtime))
+ if (testing ~= 0) then
+ luup.log('DeusExMachinaII::isBedtime(): times (mins since midnight) are now=' .. tostring(time) .. ', sunset=' .. tostring(sunset) .. ', bedtime=' .. tostring(bedtime))
end
local ret = 1 -- guilty until proven innocent
if (bedtime > sunset) then
@@ -113,7 +173,7 @@
-- Take a string and split it around sep, returning table (indexed) of substrings
-- For example abc,def,ghi becomes t[1]=abc, t[2]=def, t[3]=ghi
- -- Returns: table of values, count of values (integer >= 0)
+ -- Returns: table of values, count of values (integer ge 0)
local function split(s, sep)
local t = {}
local n = 0
@@ -134,7 +194,7 @@
until k > string.len(s)
return t, n
end
-
+
-- Return true if a specified scene has been run (i.e. on the list)
local function isSceneOn(spec)
local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
@@ -143,7 +203,7 @@
end
return false
end
-
+
-- Mark or unmark a scene as having been run
local function updateSceneState(spec, isOn)
local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
@@ -161,7 +221,7 @@
for i in pairs(t) do stateList = stateList .. "," .. tostring(i) end
luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
end
-
+
-- Run "final" scene, if defined. This scene is run after all other devices have been
-- turned off.
local function runFinalScene()
@@ -171,35 +231,38 @@
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=scene }, 0)
end
end
-
+
-- Get the list of controlled devices from our device state, parse to table of device IDs.
local function getDeviceList()
local s = luup.variable_get(SID, "Devices", lul_device) or ""
return split(s)
end
-
+
-- Light on or off? Returns boolean
local function isDeviceOn(devid)
local first = string.upper(string.sub(devid, 1, 1))
if (first == "S") then
-luup.log("DEMII::isDeviceOn(): handling scene spec " .. devid)
+luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. devid)
return isSceneOn(devid)
end
-
+
-- Handle as switch or dimmer
-luup.log("DEMII::isDeviceOn(): handling device spec " .. devid)
- local r = tonumber(string.match(devid, '%d+'), 10)
-luup.log("DEMII::isDeviceOn(): device number seems to be " .. tostring(r))
+luup.log("DeusExMachinaII::isDeviceOn(): handling device spec " .. devid)
+ local r = tonumber(string.match(devid, '^%d+'), 10)
+luup.log("DeusExMachinaII::isDeviceOn(): device number seems to be " .. tostring(r))
local val = "0"
if (luup.devices[r] ~= nil) then
- local t = luup.devices[r].device_type
- if (t == SWITCH_TYPE) then
- val = luup.variable_get(SWITCH_SID, "Status", r)
- elseif (t == DIMMER_TYPE) then
- val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
+ local t = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
+ if (t == nil) then
+ t = luup.variable_get(SWITCH_SID, "Status", r)
+ if (t == nil) then
+ luup.log("DeusExMachinaII::isDeviceOn(): device " .. tostring(devid) .. " unknown device type")
+ return nil
+ else
+ val = luup.variable_get(SWITCH_SID, "Status", r)
+ end
else
- luup.log("DeusExMachinaII::isDeviceOn(): device " .. tostring(devid) .. " unknown device_type " .. tostring(t))
- return false
+ val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
end
else
luup.log("DeusExMachinaII::isDeviceOn(): device spec " .. tostring(devid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
@@ -207,14 +270,14 @@ luup.log("DEMII::isDeviceOn(): device number seems to be " .. tostring(r))
end
return val ~= "0"
end
-
- -- Control device. Device is a string, expected to be a pure integer (in which case the device is assumed to be a switch or dimmer),
+
+ -- Control device. Device is a string, expected to be a pure integer (in which case the device is assumed to be a switch or dimmer),
-- or a string in the form Sxx:yy, in which case xx is an "on" scene to run, and yy is an "off" scene to run.
local function deviceControl(devid, turnOn)
luup.log("DeusExMachinaII::deviceControl(): devid=" .. tostring(devid) .. ", turnOn=" .. tostring(turnOn))
local first = string.upper(string.sub(devid, 1, 1))
if first == "S" then
-luup.log("DEMII::deviceControl(): handling scene spec " .. devid)
+luup.log("DeusExMachinaII::deviceControl(): handling scene spec " .. devid)
i, j, onScene, offScene = string.find(string.sub(devid, 2), "(%d+)-(%d+)")
if (i == nil) then
luup.log("DeusExMachina::deviceControl(): malformed scene spec=" .. devid)
@@ -227,7 +290,7 @@ luup.log("DEMII::deviceControl(): handling scene spec " .. devid)
luup.log("DeusExMachinaII::deviceControl(): one or both of the scenes in " .. tostring(devid) .. " not found in luup.scenes!")
return
end
-luup.log("DEMII::deviceControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
+luup.log("DeusExMachinaII::deviceControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
local targetScene
if (turnOn) then targetScene = onScene else targetScene = offScene end
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
@@ -240,23 +303,29 @@ luup.log("DEMII::deviceControl(): on scene is " .. tostring(onScene) .. ", off s
lvl = tonumber(lvl, 10)
end
devid = tonumber(devid, 10)
-luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", level " .. tostring(lvl))
+luup.log("DeusExMachinaII::deviceControl(): handling device " .. tostring(devid) .. ", level " .. tostring(lvl))
if luup.devices[devid] == nil then
luup.log("DeusExMachinaII::deviceControl(): device " .. tostring(devid) .. " not found in luup.devices");
return
end
- local t = luup.devices[devid].device_type
- if not turnOn then
- lvl = 0
- end
- if (t == DIMMER_TYPE) then
- luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, devid) -- note odd case inconsistency
- elseif (t == SWITCH_TYPE) then
- if turnOn then lvl = 1 end
- luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, devid)
+ -- Level for all types is 0 if turning device off
+ if not turnOn then lvl = 0 end
+ local t = luup.variable_get(DIMMER_SID, "LoadLevelTarget", devid)
+ if (t == nil) then
+ t = luup.variable_get(SWITCH_SID, "Status", devid)
+ if (t == nil) then
+ luup.log("DeusExMachinaII: deviceControl(): can't determine device type of devspec=" .. devid)
+ else
+ -- Handle as SwitchPower1
+ if turnOn then lvl = 1 end
+luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as binary light, setting target to " .. lvl)
+ luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, devid)
+ end
else
- luup.log("DeusExMachinaII: deviceControl(): can't determine device type of devspec=" .. devid)
- end
+ -- Handle as Dimming1
+luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer, setting load level to " .. lvl)
+ luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, devid) -- note odd case inconsistency
+ end
end
end
@@ -269,7 +338,8 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
local on = {}
local n = 0
for i = 1,max do
- if (isDeviceOn(devs[i])) then
+ local devOn = isDeviceOn(devs[i])
+ if (devOn ~= nil and devOn) then
table.insert(on, devs[i])
n = n + 1
end
@@ -279,15 +349,29 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
deviceControl(on[i], false)
luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(on[i]) .. " OFF, " .. tostring(n-1) .. " devices still on.")
if (n > 1) then
- return 1
+ return true -- there are still lights to turn off
end
end
end
- return 0
+ return false -- no more lights to turn off
+ end
+
+ -- Turn off all lights as fast as we can. Transition through SHUTDOWN state during,
+ -- in case user has any triggers connected to that state. The caller must immediately
+ -- set the next state when this function returns (expected would be STANDBY or IDLE).
+ function clearLights()
+ local devs, count
+ devs, count = getDeviceList()
+ luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
+ while count > 0 do
+ deviceControl(devs[count], false)
+ count = count - 1
+ end
+ runFinalScene()
end
-
+
-- runOnce() looks to see if a core state variable exists; if not, a one-time initialization
- -- takes place. For us, that means looking to see if an older version of Deus is still
+ -- takes place. For us, that means looking to see if an older version of Deus is still
-- installed, and copying its config into our new config. Then disable the old Deus.
local function runOnce()
local s = luup.variable_get(SID, "Enabled", lul_device)
@@ -328,11 +412,11 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
luup.variable_set(SID, "Devices", s, lul_device)
deleteVar("controlCount", lul_device)
end
-
+
-- Finally, turn off old Deus
luup.call_action(oldsid, "SetEnabled", { NewEnabledValue = "0" }, olddev)
end
-
+
-- Set up some other default config
luup.variable_set(SID, "MinCycleDelay", "300", lul_device)
luup.variable_set(SID, "MaxCycleDelay", "1800", lul_device)
@@ -358,12 +442,12 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
end
end
deleteVar("LightsOutTime", lul_device)
-
+
-- Update version last.
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
end
-
+
-- Enable DEM by setting a new cycle stamp and calling an initial cycle directly.
function deusEnable()
luup.log("DeusExMachinaII::deusEnable(): enabling...")
@@ -375,37 +459,32 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
-- turn off any controlled lights that are on.
function deusDisable()
- local state = getVarNumeric("State", STATE_STANDBY)
+ local s = getVarNumeric("State", STATE_STANDBY)
luup.log("DeusExMachinaII::deusDisable(): disabling...")
- if ( state == STATE_CYCLE or state == STATE_SHUTDOWN ) then
- local devs, count
- devs, count = getDeviceList()
- while count > 0 do
- deviceControl(devs[count], false)
- count = count - 1
- end
+ if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
+ clearLights()
end
luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
end
-
+
-- Initialize.
function deusInit(deusDevice)
luup.log("DeusExMachinaII::deusInit(): Version 2.3 (2016-08-20), starting up...")
-
+
-- One-time stuff
runOnce()
-
+
--check UI version
checkVersion()
-
+
-- Start up if we're enabled
- if (isEnabled() == 1) then
+ if (isEnabled()) then
deusEnable()
else
deusDisable()
end
end
-
+
-- Run a cycle. If we're in "bedtime" (i.e. not between our cycle period between sunset and stop),
-- then we'll shut off any lights we've turned on and queue another run for the next sunset. Otherwise,
-- we'll toggled one of our controlled lights, and queue (random delay, but soon) for another cycle.
@@ -415,124 +494,107 @@ luup.log("DEMII::deviceControl(): handling device " .. tostring(devid) .. ", lev
local stepStamp = tonumber(stepStampCheck)
luup.log("DeusExMachinaII::deusStep(): wakeup, stamp " .. stepStampCheck)
if (stepStamp ~= runStamp) then
- luup.log("DeusExMachinaII::deusStep(): another thread running, skipping")
+ luup.log("DeusExMachinaII::deusStep(): stamp mismatch, another thread running. Bye!")
return
end
- if (isEnabled() ~= 1) then
- luup.log("DeusExMachinaII::deusStep(): not enabled, skipping")
+ if (not isEnabled()) then
+ luup.log("DeusExMachinaII::deusStep(): not enabled, no more work for this thread...")
return
end
- local modebits = getVarNumeric("HouseModes", 0)
- -- local currentMode = luup.attr_get("Mode")
- local currentMode
- local status
- status, currentMode = luup.inet.wget("http://127.0.0.1:3480/data_request?id=variableget&Variable=Mode",0)
-
+ -- Get next sunset as seconds since midnight (approx)
+ local sunset = getSunset()
+
local currentState = getVarNumeric("State", 0)
- if (currentState == 0 or currentState == 1) then
- luup.log("DeusExMachinaII:deusStep(): run in state "
+ if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
+ luup.log("DeusExMachinaII::deusStep(): run in state "
.. tostring(currentState)
- .. ", modebits=" .. tostring(modebits)
- .. ", currentMode=" .. tostring(currentMode)
.. ", lightsout=" .. tostring(luup.variable_get(SID, "LightsOut", lul_device))
- .. ", luup.sunset=" .. tostring(luup.sunset())
+ .. ", sunset=" .. tostring(sunset)
.. ", os.time=" .. tostring(os.time())
)
- luup.log("+++ longitude="
+ luup.log("+ long="
.. tostring(luup.longitude)
- .. ", latitude=" .. tostring(luup.latitude)
- .. ", timezone=" .. tostring(luup.timezone)
+ .. ", lat=" .. tostring(luup.latitude)
+ .. ", tz=" .. tostring(luup.timezone)
.. ", city=" .. tostring(luup.city)
+ .. ", luup.sunset=" .. tostring(luup.sunset())
.. ", version=" .. tostring(luup.version)
)
end
- local runCycle = 1
-
- -- Check to see if house mode bits are non-zero, and if so, apply current mode as mask.
- -- If bit is set (current mode is in the bitset), we can run, otherwise skip.
- if (modebits ~= 0) then
- local bit = require("bit")
- -- Get the current house mode (1=Home,2=Away,3=Night,4=Vacation)
- currentMode = math.pow(2, tonumber(currentMode,10))
- if (bit.band(modebits, currentMode) == 0) then
- luup.log('DeusExMachinaII::deusStep(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' not in set ' .. string.format("0x%x", modebits))
- runCycle = 0
- end
- end
-
- if (runCycle and isBedtime() ~= 0) then
- luup.log("DeusExMachinaII::deusStep(): lights out time")
- runCycle = 0
- end
-
- -- See if we're in test mode. If so, compute possible sunset override (debug)
- local mindelay, maxdelay
- local sunset = luup.sunset()
- local now = os.time()
- local testing = getVarNumeric("TestMode", 0)
- if (testing ~= 0) then
- -- Note that TestSunset is expressed in MINUTES since midnight
- local s = getVarNumeric("TestSunset", nil)
- if (s ~= nil) then
- local t = tonumber(s, 10)
- local hh = math.floor(t / 60)
- local mm = math.mod(t, 60)
- t = os.date('*t')
- t['hour'] = hh
- t['min'] = mm
- t['sec'] = 0
- sunset = os.time(t)
- if ( sunset <= now ) then sunset = sunset + 86400 end
- luup.log('DeusExMachinaII::deusStep(): TestMode is on, next sunset is ' .. tostring(sunset) .. " (" .. tostring(sunset-os.time()) .. " from now)")
- end
+ local inActiveTimePeriod = true
+ if (isBedtime() ~= 0) then
+ luup.log("DeusExMachinaII::deusStep(): in lights out time")
+ inActiveTimePeriod = false
end
- -- See if we've crossed the lights-out time
- if (runCycle == 0) then
+ -- Get going...
+ local nextCycleDelay = 300 -- a default value to keep us out of hot water
+ if (not isActiveHouseMode()) then
+ -- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
+ if (currentState ~= STATE_IDLE) then
+ luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE, not in an active house mode.");
+ if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
+ luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ else
+ luup.log("DeusExMachinaII::deusStep(): IDLE in an inactive house mode; waiting for mode change.");
+ end
+
+ -- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
+ -- to re-check house mode, which could change at any time, so we must deal with it.
+ if (inActiveTimePeriod) then
+ nextCycleDelay = 300
+ else
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ end
+ elseif (not inActiveTimePeriod) then
luup.log("DeusExMachinaII::deusStep(): running off cycle")
luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
- mindelay = getVarNumeric("MinOffDelay", 60)
- maxdelay = getVarNumeric("MaxOffDelay", 300)
- if (turnOffLight() == 0) then
- -- No more lights to turn off, arm for next sunset
+ if (not turnOffLight()) then
+ -- No more lights to turn off
runFinalScene()
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
- local delay = sunset - os.time() + math.random(mindelay,maxdelay)
- luup.log("DeusExMachina::deusStep(): all lights out; waiting for next sunset in " .. delay)
- luup.call_delay("deusStep", delay, stepStamp, 1)
- return
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ luup.log("DeusExMachina::deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
+ else
+ nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
end
else
+ -- Fully active. Find a random device to control and control it.
luup.log("DeusExMachinaII::deusStep(): running toggle cycle")
luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
- mindelay = getVarNumeric("MinCycleDelay", 300)
- maxdelay = getVarNumeric("MaxCycleDelay", 1800)
+ nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
local devs, max
devs, max = getDeviceList()
if (max > 0) then
local change = math.random(1, max)
local devspec = devs[change]
if (devspec ~= nil) then
- if (isDeviceOn(devspec)) then
- deviceControl(devspec, false)
- luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to OFF")
- else
- deviceControl(devspec, true)
- luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to ON")
+ local s = isDeviceOn(devspec)
+ if (s ~= nil) then
+ if (s) then
+ deviceControl(devspec, false)
+ luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to OFF")
+ else
+ deviceControl(devspec, true)
+ luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to ON")
+ end
end
end
else
luup.log("DeusExMachinaII:deusStep(): no devices to control")
end
end
-
+
-- Arm for next cycle
- -- PHR??? Should we sure delay doesn't roll past LightsOut? Does it matter?
- local delay = math.random(mindelay, maxdelay)
- luup.call_delay("deusStep", delay, stepStamp, 1)
- luup.log("DeusExMachinaII::deusStep(): cycle finished, next in " .. delay .. " seconds")
+ if nextCycleDelay ~= nil then
+ luup.log("DeusExMachinaII::deusStep(): cycle finished, next in " .. nextCycleDelay .. " seconds")
+ if nextCycleDelay < 1 then nextCycleDelay = 60 end
+ luup.call_delay("deusStep", nextCycleDelay, stepStamp, 1)
+ else
+ luup.log("DeusExMachinaII::deusStep(): nil nextCycleDelay, next cycle not scheduled!");
+ end
end
deusInit
From b65cdd90cbbed9b386c61e5b629bc7f7490dd854 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 25 Sep 2016 13:36:12 -0400
Subject: [PATCH 04/49] Notes about new device list
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 50e4dac..349c8c5 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,9 @@ If no modes are chosen, it is the same as choosing all modes (Deus operates in a
Selecting the lights to be controlled is a simple matter of clicking the check boxes. Lights on dimmers cannot be set to values less than 100% in the current version of the plugin. Because the operating cycle of
the plug-in is random, any controlled light may be turned on and off several times during the cycling period (between sunset and Lights Out time).
+As of version 2.4, all devices are listed that implement the SwitchPower1 and Dimming1 services. This leads to some oddities,
+like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example, might do when you try to turn it off and on like a light, so be careful selecting these devices.
+
#### Control by Scene ####
As of version 2.0 and on UI7, DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
From 2b2e394069d5e36524e8c39dffd09c3f94b2feab Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 25 Sep 2016 13:47:13 -0400
Subject: [PATCH 05/49] More about scene control in 2.4
---
README.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/README.md b/README.md
index 349c8c5..76cc514 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,14 @@ the plug-in is random, any controlled light may be turned on and off several tim
As of version 2.4, all devices are listed that implement the SwitchPower1 and Dimming1 services. This leads to some oddities,
like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example, might do when you try to turn it off and on like a light, so be careful selecting these devices.
+Also new for version 2.4 is the ability to run scenes during the random cycling period. Scenes must be specified in pairs, with
+one being the "on" scene and the other being an "off" scene. This not allows more patterned use of lights, but also gives user
+the ability to handle device-specific capabilities that would be difficult to track in DEMII. For example, while DEMII can now
+turn Philips Hue lights on and off (to dimming levels, even), it cannot control their color because there's no UI for that in
+DEMII. But a scene could be used to control that light or a group of lights, with their color.
+
+Finally, 2.4 adds the ability for a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
+
#### Control by Scene ####
As of version 2.0 and on UI7, DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
From 33a699e001edcf45d9913269d8e2e8657afebd72 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 25 Sep 2016 14:16:32 -0400
Subject: [PATCH 06/49] Update version number for release candidate
---
I_DeusExMachinaII1.xml | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 7f0dee7..101d8b0 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -13,7 +13,7 @@
SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
- DEMVERSION = 20302
+ DEMVERSION = 20400
STATE_STANDBY = 0
STATE_IDLE = 1
@@ -428,13 +428,13 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
end
-- Consider per-version changes.
- -- v2.3: LightsOutTime (in milliseconds) deprecated, now using LightsOut (in minutes since midnight)
s = getVarNumeric("Version", 0)
- if (s < DEMVERSION) then
- luup.log("DeusExMachinaII::runOnce(): updating config, version " .. tostring(s) .. " < " .. DEMVERSION)
+ if (s < 20300) then
+ -- v2.3: LightsOutTime (in milliseconds) deprecated, now using LightsOut (in minutes since midnight)
+ luup.log("DeusExMachinaII::runOnce(): updating config, version " .. tostring(s) .. " < 20300")
s = luup.variable_get(SID, "LightsOut", lul_device)
if (s == nil) then
- s = getVarNumeric("LightsOutTime") -- get pre-2.2 variable
+ s = getVarNumeric("LightsOutTime") -- get pre-2.3 variable
if (s == nil) then
luup.variable_set(SID, "LightsOut", 1439, lul_device) -- default 23:59
else
@@ -442,10 +442,10 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
end
end
deleteVar("LightsOutTime", lul_device)
-
- -- Update version last.
- luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
+
+ -- Update version last.
+ luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
-- Enable DEM by setting a new cycle stamp and calling an initial cycle directly.
@@ -469,7 +469,7 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
-- Initialize.
function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.3 (2016-08-20), starting up...")
+ luup.log("DeusExMachinaII::deusInit(): Version 2.4RC1 (2016-09-25), starting up...")
-- One-time stuff
runOnce()
From 4b897d4ee03153749935204cf0db1f3b1841d3d5 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 25 Sep 2016 14:16:48 -0400
Subject: [PATCH 07/49] Update version number for release candidate
---
D_DeusExMachinaII1_UI7.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index 7bb8eb8..4a4b0fc 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -103,7 +103,7 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.3 2016-08-20 For documentation or to report bugs, please go to the DeusExMachina Github repository ."
+ "text": "DeusExMachina II ver 2.4RC1 2016-09-25 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks of use without limitation. ."
},
"Display": {
"Top": "80",
From ba767a1994fda99069043604be0a35b737303c64 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Wed, 28 Sep 2016 09:38:04 -0400
Subject: [PATCH 08/49] Fix what Deus does when we enable in inactive period.
It was just cycling lights off as if it had been running all along. Now touch
nothing, but go to idle state and delay to next sunset + random.
---
I_DeusExMachinaII1.xml | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 101d8b0..d02c70c 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -528,10 +528,16 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
luup.log("DeusExMachinaII::deusStep(): in lights out time")
inActiveTimePeriod = false
end
-
+
-- Get going...
local nextCycleDelay = 300 -- a default value to keep us out of hot water
- if (not isActiveHouseMode()) then
+ if (currentState == STATE_STANDBY and not inActiveTimePeriod) then
+ -- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
+ -- Go to IDLE and delay for next sunset.
+ luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...");
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ elseif (not isActiveHouseMode()) then
-- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
if (currentState ~= STATE_IDLE) then
luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE, not in an active house mode.");
From 49fa9fde1684af8e4b153500ce8233dc15dd47d4 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Thu, 15 Dec 2016 19:10:24 -0500
Subject: [PATCH 09/49] UI support for max number of targets allowed to be "on"
at once.
---
J_DeusExMachinaII1_UI7.js | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index 63b5eaf..a80f5c8 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -237,6 +237,17 @@ var DeusExMachinaII = (function(api) {
}
alert("Time must be in the format HH:MM (i.e. 22:30)");
}
+
+ function checkMaxTargets()
+ {
+ var maxt = jQuery("#maxtargets").val();
+ var re = new RegExp("^[0-9]+$");
+ if (re.exec(maxt)) {
+ api.setDeviceStatePersistent(deusDevice, serviceId, "MaxTargetsOn", maxt, 0);
+ return;
+ }
+ alert("Max On Targets must be an integer and >= 0");
+ }
////////////////////////////
function configureDeus()
@@ -259,7 +270,9 @@ var DeusExMachinaII = (function(api) {
html += '.color-red { color: #ff0000; }';
html += '.color-green { color: #12805b; }';
html += 'input#deusExTime { text-align: center; }';
+ html += 'input#maxtargets { text-align: center; }';
html += '';
+
html += "Lights-Out Time Lights will cycle between sunset and the \"lights-out\" time. Enter the time to begin shutting off lights: ";
html += " (HH:MM)";
@@ -320,16 +333,20 @@ var DeusExMachinaII = (function(api) {
// Handle scene pairs
html += 'Scene Control ';
- html += 'In addition to controlling individual devices, DeusExMachinaII can also run scenes. Scenes are specified in pairs: a scene to do something (the "on" scene), and a scene to undo it (the "off" scene). To add a scene pair, select an "on" scene and an "off" scene and click the green plus. To remove a configured scene pair, click the red minus next to it.';
+ html += 'In addition to controlling individual devices, DeusExMachinaII can run scenes. Scenes are specified in pairs: a scene to do something (the "on" scene), and a scene to undo it (the "off" scene). To add a scene pair, select an "on" scene and an "off" scene and click the green plus. To remove a configured scene pair, click the red minus next to it.';
html += '
Add Scene Pair: On Scene:--choose-- ';
html += ' Off Scene:--choose-- ';
html += ' ';
html += ' add_circle_outline '
html += '';
html += ' ';
-
- // Final scene
- html += 'Final Scene The final scene, if specified, is run after all other devices have been turned off during a lights-out cycle.
Final Scene: (none) ';
+
+ // Maximum number of targets allowed to be "on" simultaneously
+ html += "Maximum \"On\" Targets Maximum number of controlled devices and scenes (targets) that can be \"on\" at once: ";
+ html += " (0=no limit)";
+
+ // Final scene (the scene that is run when everything has been turned off and DEM is going idle).
+ html += '
Final Scene The final scene, if specified, is run after all other targets have been turned off during a lights-out cycle.Final Scene: (none) ';
html += ' ';
html += 'More Information If you need more information about configuring DeusExMachinaII, please see the README in our GitHub repository .';
@@ -343,6 +360,12 @@ var DeusExMachinaII = (function(api) {
if (!isNaN(timeMins))
time = timeMinsToStr(timeMins);
jQuery("#deusExTime").val(time);
+
+ // Restore maxtargets
+ var maxt = parseInt(api.getDeviceState(deusDevice, serviceId, "MaxTargetsOn"));
+ if (isNaN(maxt) || maxt < 0)
+ maxt = 0;
+ jQuery("#maxtargets").val(maxt);
// Restore house modes
var houseModes = parseInt(api.getDeviceState(deusDevice, serviceId, "HouseModes"));
@@ -416,6 +439,7 @@ console.log('found for ' + info.type + ' ' + info.device + ' with value=' + info
onBeforeCpanelClose: onBeforeCpanelClose,
changeHouseModeSelector: changeHouseModeSelector,
checkTime: checkTime,
+ checkMaxTargets: checkMaxTargets,
updateDeusControl: updateDeusControl,
configureDeus: configureDeus,
addScenePair: addScenePair,
From fe77d0bb9071f5c573598342386d75130543461b Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Thu, 15 Dec 2016 19:10:49 -0500
Subject: [PATCH 10/49] Support for max number of targets allowed on at once.
Now RC2.
---
I_DeusExMachinaII1.xml | 134 ++++++++++++++++++++++++-----------------
1 file changed, 78 insertions(+), 56 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index d02c70c..a753ac2 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -222,7 +222,7 @@
luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
end
- -- Run "final" scene, if defined. This scene is run after all other devices have been
+ -- Run "final" scene, if defined. This scene is run after all other targets have been
-- turned off.
local function runFinalScene()
local scene = getVarNumeric("FinalScene", nil)
@@ -232,31 +232,31 @@
end
end
- -- Get the list of controlled devices from our device state, parse to table of device IDs.
- local function getDeviceList()
+ -- Get the list of targets from our device state, parse to table of targets.
+ local function getTargetList()
local s = luup.variable_get(SID, "Devices", lul_device) or ""
return split(s)
end
-- Light on or off? Returns boolean
- local function isDeviceOn(devid)
- local first = string.upper(string.sub(devid, 1, 1))
+ local function isDeviceOn(targetid)
+ local first = string.upper(string.sub(targetid, 1, 1))
if (first == "S") then
-luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. devid)
- return isSceneOn(devid)
+luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. targetid)
+ return isSceneOn(targetid)
end
-- Handle as switch or dimmer
-luup.log("DeusExMachinaII::isDeviceOn(): handling device spec " .. devid)
- local r = tonumber(string.match(devid, '^%d+'), 10)
-luup.log("DeusExMachinaII::isDeviceOn(): device number seems to be " .. tostring(r))
+luup.log("DeusExMachinaII::isDeviceOn(): handling target spec " .. targetid)
+ local r = tonumber(string.match(targetid, '^%d+'), 10)
+luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
local val = "0"
if (luup.devices[r] ~= nil) then
local t = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
if (t == nil) then
t = luup.variable_get(SWITCH_SID, "Status", r)
if (t == nil) then
- luup.log("DeusExMachinaII::isDeviceOn(): device " .. tostring(devid) .. " unknown device type")
+ luup.log("DeusExMachinaII::isDeviceOn(): target " .. tostring(targetid) .. " unknown device type")
return nil
else
val = luup.variable_get(SWITCH_SID, "Status", r)
@@ -265,78 +265,78 @@ luup.log("DeusExMachinaII::isDeviceOn(): device number seems to be " .. tostring
val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
end
else
- luup.log("DeusExMachinaII::isDeviceOn(): device spec " .. tostring(devid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
+ luup.log("DeusExMachinaII::isDeviceOn(): target spec " .. tostring(targetid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
return false
end
return val ~= "0"
end
- -- Control device. Device is a string, expected to be a pure integer (in which case the device is assumed to be a switch or dimmer),
+ -- Control target. Target is a string, expected to be a pure integer (in which case the target is assumed to be a switch or dimmer),
-- or a string in the form Sxx:yy, in which case xx is an "on" scene to run, and yy is an "off" scene to run.
- local function deviceControl(devid, turnOn)
- luup.log("DeusExMachinaII::deviceControl(): devid=" .. tostring(devid) .. ", turnOn=" .. tostring(turnOn))
- local first = string.upper(string.sub(devid, 1, 1))
+ local function targetControl(targetid, turnOn)
+ luup.log("DeusExMachinaII::targetControl(): targetid=" .. tostring(targetid) .. ", turnOn=" .. tostring(turnOn))
+ local first = string.upper(string.sub(targetid, 1, 1))
if first == "S" then
-luup.log("DeusExMachinaII::deviceControl(): handling scene spec " .. devid)
- i, j, onScene, offScene = string.find(string.sub(devid, 2), "(%d+)-(%d+)")
+luup.log("DeusExMachinaII::targetControl(): handling scene spec " .. targetid)
+ i, j, onScene, offScene = string.find(string.sub(targetid, 2), "(%d+)-(%d+)")
if (i == nil) then
- luup.log("DeusExMachina::deviceControl(): malformed scene spec=" .. devid)
+ luup.log("DeusExMachina::targetControl(): malformed scene spec=" .. targetid)
return
end
onScene = tonumber(onScene, 10)
offScene = tonumber(offScene, 10)
if luup.scenes[onScene] == nil or luup.scenes[offScene] == nil then
-- Both on scene and off scene must exist (defensive).
- luup.log("DeusExMachinaII::deviceControl(): one or both of the scenes in " .. tostring(devid) .. " not found in luup.scenes!")
+ luup.log("DeusExMachinaII::targetControl(): one or both of the scenes in " .. tostring(targetid) .. " not found in luup.scenes!")
return
end
-luup.log("DeusExMachinaII::deviceControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
+luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
local targetScene
if (turnOn) then targetScene = onScene else targetScene = offScene end
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
- updateSceneState(devid, turnOn)
+ updateSceneState(targetid, turnOn)
else
local lvl = 100
- local k = string.find(devid, '=')
+ local k = string.find(targetid, '=')
if k ~= nil then
- _, _, devid, lvl = string.find(devid, "(%d+)=(%d+)")
+ _, _, targetid, lvl = string.find(targetid, "(%d+)=(%d+)")
lvl = tonumber(lvl, 10)
end
- devid = tonumber(devid, 10)
-luup.log("DeusExMachinaII::deviceControl(): handling device " .. tostring(devid) .. ", level " .. tostring(lvl))
- if luup.devices[devid] == nil then
- luup.log("DeusExMachinaII::deviceControl(): device " .. tostring(devid) .. " not found in luup.devices");
+ targetid = tonumber(targetid, 10)
+luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(targetid) .. ", level " .. tostring(lvl))
+ if luup.devices[targetid] == nil then
+ luup.log("DeusExMachinaII::targetControl(): device " .. tostring(targetid) .. " not found in luup.devices");
return
end
-- Level for all types is 0 if turning device off
if not turnOn then lvl = 0 end
- local t = luup.variable_get(DIMMER_SID, "LoadLevelTarget", devid)
+ local t = luup.variable_get(DIMMER_SID, "LoadLevelTarget", targetid)
if (t == nil) then
- t = luup.variable_get(SWITCH_SID, "Status", devid)
+ t = luup.variable_get(SWITCH_SID, "Status", targetid)
if (t == nil) then
- luup.log("DeusExMachinaII: deviceControl(): can't determine device type of devspec=" .. devid)
+ luup.log("DeusExMachinaII: targetControl(): can't determine device type of devspec=" .. targetid)
else
-- Handle as SwitchPower1
if turnOn then lvl = 1 end
-luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as binary light, setting target to " .. lvl)
- luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, devid)
+luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
+ luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
end
else
-- Handle as Dimming1
-luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer, setting load level to " .. lvl)
- luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, devid) -- note odd case inconsistency
+luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
+ luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
end
end
end
-
- -- Turn off a light, if any is on. Returns 1 if there are more lights to turn off; otherwise 0.
- local function turnOffLight()
+
+ -- Get list of targets that are on
+ local function getTargetsOn()
local devs, max
- devs, max = getDeviceList()
+ local on = {}
+ local n = 0
+ devs,max = getTargetList()
if (max > 0) then
local i
- local on = {}
- local n = 0
for i = 1,max do
local devOn = isDeviceOn(devs[i])
if (devOn ~= nil and devOn) then
@@ -344,13 +344,21 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
n = n + 1
end
end
- if (n > 0) then
- i = math.random(1, n)
- deviceControl(on[i], false)
- luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(on[i]) .. " OFF, " .. tostring(n-1) .. " devices still on.")
- if (n > 1) then
- return true -- there are still lights to turn off
- end
+ end
+ return on,n
+ end
+
+ -- Turn off a light, if any is on. Returns 1 if there are more lights to turn off; otherwise 0.
+ local function turnOffLight()
+ local on
+ local n
+ on, n = getTargetsOn()
+ if (n > 0) then
+ local i = math.random(1, n)
+ targetControl(on[i], false)
+ luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(on[i]) .. " OFF, " .. tostring(n-1) .. " targets still on.")
+ if (n > 1) then
+ return true -- there are still lights to turn off
end
end
return false -- no more lights to turn off
@@ -361,10 +369,10 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
-- set the next state when this function returns (expected would be STANDBY or IDLE).
function clearLights()
local devs, count
- devs, count = getDeviceList()
+ devs, count = getTargetList()
luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
while count > 0 do
- deviceControl(devs[count], false)
+ targetControl(devs[count], false)
count = count - 1
end
runFinalScene()
@@ -423,6 +431,7 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
luup.variable_set(SID, "MinOffDelay", "60", lul_device)
luup.variable_set(SID, "MaxOffDelay", "300", lul_device)
luup.variable_set(SID, "LightsOut", 1439, lul_device)
+ luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
luup.variable_set(SID, "Enabled", "0", lul_device)
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
@@ -469,7 +478,7 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
-- Initialize.
function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.4RC1 (2016-09-25), starting up...")
+ luup.log("DeusExMachinaII::deusInit(): Version 2.4RC2 (2016-12-15), starting up...")
-- One-time stuff
runOnce()
@@ -567,12 +576,12 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
end
else
- -- Fully active. Find a random device to control and control it.
+ -- Fully active. Find a random target to control and control it.
luup.log("DeusExMachinaII::deusStep(): running toggle cycle")
luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
local devs, max
- devs, max = getDeviceList()
+ devs, max = getTargetList()
if (max > 0) then
local change = math.random(1, max)
local devspec = devs[change]
@@ -580,16 +589,29 @@ luup.log("DeusExMachinaII: deviceControl(): handling " .. devid .. " as dimmer,
local s = isDeviceOn(devspec)
if (s ~= nil) then
if (s) then
- deviceControl(devspec, false)
+ -- Turn something off.
+ targetControl(devspec, false)
luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to OFF")
else
- deviceControl(devspec, true)
+ -- Turn something on. If we're at the max number of targets we're allowed to turn on,
+ -- turn targets off first.
+ local maxOn = getVarNumeric("MaxTargetsOn")
+ if (maxOn == nil) then maxOn = 0 else maxOn = tonumber(maxOn,10) end
+ if (maxOn > 0) then
+ local on, n
+ on, n = getTargetsOn()
+ while ( n >= maxOn ) do
+ turnOffLight()
+ on, n = getTargetsOn()
+ end
+ end
+ targetControl(devspec, true)
luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to ON")
end
end
end
else
- luup.log("DeusExMachinaII:deusStep(): no devices to control")
+ luup.log("DeusExMachinaII:deusStep(): no targets to control")
end
end
From 8ec4f5c5f1ea59862aaa5fe3adb3a042ac2c49ab Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Thu, 15 Dec 2016 19:11:43 -0500
Subject: [PATCH 11/49] Now RC2
---
D_DeusExMachinaII1_UI7.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index 4a4b0fc..bf2f81e 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -103,7 +103,7 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4RC1 2016-09-25 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks of use without limitation. ."
+ "text": "DeusExMachina II ver 2.4RC2 2016-12-15 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks of use without limitation. ."
},
"Display": {
"Top": "80",
From 08d86c97f3571058895c08435c7d24a4e5496d4d Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Thu, 15 Dec 2016 20:10:03 -0500
Subject: [PATCH 12/49] Additional features for 2.4RC2. Prep for release.
---
README.md | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 76cc514..f64d92b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ There are currently two versions of Deus Ex Machina available:
* Deus Ex Machina -- version 1.1, for UI5; this is the legacy version and although it installs for UI6 and UI7, it does not work properly on those platforms.
-* Deus Ex Machina II -- version 2.0, for UI7; this is the new plugin. It is for all versions of firmware, but has only been tested under UI7. Testing and bug reports for UI5 and UI6 would be appreciated.
+* Deus Ex Machina II -- version 2.4, for UI7 (only). This version was developed and tested on firmware version 1.7.855, but should work for any full release of UI7 provided by MiCasaVerde.
### History ###
@@ -59,8 +59,9 @@ UI7 introduced the concept of "House Modes." Version 2.3 and beyond of Deus Ex M
house is in one or more selected house modes. A set of checkboxes is used to selected which modes allow Deus Ex Machina to run.
If no modes are chosen, it is the same as choosing all modes (Deus operates in any house mode).
-Selecting the lights to be controlled is a simple matter of clicking the check boxes. Lights on dimmers cannot be set to values less than 100% in the current version of the plugin. Because the operating cycle of
-the plug-in is random, any controlled light may be turned on and off several times during the cycling period (between sunset and Lights Out time).
+Selecting the devices to be controlled is a simple matter of clicking the check boxes. Because the operating cycle of
+the plug-in is random, any controlled device may be turned on and off several times during the cycling period (between sunset and Lights Out time).
+As of version 2.4, lights on dimmers can be set to any level by setting the slider that appears to the right of the device name. Non-dimming devices are simply turned on and off.
As of version 2.4, all devices are listed that implement the SwitchPower1 and Dimming1 services. This leads to some oddities,
like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example, might do when you try to turn it off and on like a light, so be careful selecting these devices.
@@ -71,6 +72,8 @@ the ability to handle device-specific capabilities that would be difficult to tr
turn Philips Hue lights on and off (to dimming levels, even), it cannot control their color because there's no UI for that in
DEMII. But a scene could be used to control that light or a group of lights, with their color.
+Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
+
Finally, 2.4 adds the ability for a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
#### Control by Scene ####
@@ -89,8 +92,8 @@ luup.call_action("urn:futzle-com:serviceId:DeusExMachina1", "SetEnabled", { NewE
Of course, only one of either "0" or "1" should be specified.
-Note that when disabling Deus Ex Machina from a scene or the user interface, versions 1.1 and 2.0 operate differently. Version 1.1 will simply stop cycling lights, leaving on any controlled lights it may have turned on. Version 2, however,
-will turn off all controlled lights _if it was in the cycling period (between sunset and lights out time) at the time it was disabled_.
+Note that when disabling Deus Ex Machina from a scene or the user interface, versions 1.1 and 2.0 operate differently. Version 1.1 will simply stop cycling lights, leaving on any controlled lights it may have turned on.
+Version 2, however, will turn off all controlled lights _if it was in the cycling period (between sunset and lights out time) at the time it was disabled_.
Version 2.0 also added the ability for a change of DeusExMachina's Enabled state to be used as trigger in scenes and other places where events can be watched (e.g. Program Logic plugins, etc.). This also works on UI7 only.
From a77924478ffcddd636d4796cd694655dae3a1ae0 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Thu, 15 Dec 2016 20:13:48 -0500
Subject: [PATCH 13/49] Text tweaks.
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index f64d92b..2b66910 100644
--- a/README.md
+++ b/README.md
@@ -67,12 +67,12 @@ As of version 2.4, all devices are listed that implement the SwitchPower1 and Di
like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example, might do when you try to turn it off and on like a light, so be careful selecting these devices.
Also new for version 2.4 is the ability to run scenes during the random cycling period. Scenes must be specified in pairs, with
-one being the "on" scene and the other being an "off" scene. This not allows more patterned use of lights, but also gives user
-the ability to handle device-specific capabilities that would be difficult to track in DEMII. For example, while DEMII can now
+one being the "on" scene and the other being an "off" scene. This not only allows more patterned use of lights, but also gives the user
+the ability to handle device-specific capabilities that would be difficult to implement in DEMII. For example, while DEMII can now
turn Philips Hue lights on and off (to dimming levels, even), it cannot control their color because there's no UI for that in
DEMII. But a scene could be used to control that light or a group of lights, with their color.
-Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
+Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously. If this limit is 0, there is no limit enforced.
Finally, 2.4 adds the ability for a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
From b2d9b33910372b679660842289074e2098ac7f04 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 17 Dec 2016 09:15:16 -0500
Subject: [PATCH 14/49] WIP before change to device type sensing
---
I_DeusExMachinaII1.xml | 46 +++++++++++++++++++++++-------------------
1 file changed, 25 insertions(+), 21 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index a753ac2..56a3e70 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -242,14 +242,14 @@
local function isDeviceOn(targetid)
local first = string.upper(string.sub(targetid, 1, 1))
if (first == "S") then
-luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. targetid)
+ -- luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. targetid)
return isSceneOn(targetid)
end
-- Handle as switch or dimmer
-luup.log("DeusExMachinaII::isDeviceOn(): handling target spec " .. targetid)
+ -- luup.log("DeusExMachinaII::isDeviceOn(): handling target spec " .. targetid)
local r = tonumber(string.match(targetid, '^%d+'), 10)
-luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
+ -- luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
local val = "0"
if (luup.devices[r] ~= nil) then
local t = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
@@ -303,13 +303,13 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
lvl = tonumber(lvl, 10)
end
targetid = tonumber(targetid, 10)
-luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(targetid) .. ", level " .. tostring(lvl))
+ -- Level for all types is 0 if turning device off
+ if not turnOn then lvl = 0 end
+ -- luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(targetid) .. ", level " .. tostring(lvl))
if luup.devices[targetid] == nil then
luup.log("DeusExMachinaII::targetControl(): device " .. tostring(targetid) .. " not found in luup.devices");
return
end
- -- Level for all types is 0 if turning device off
- if not turnOn then lvl = 0 end
local t = luup.variable_get(DIMMER_SID, "LoadLevelTarget", targetid)
if (t == nil) then
t = luup.variable_get(SWITCH_SID, "Status", targetid)
@@ -318,12 +318,12 @@ luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(target
else
-- Handle as SwitchPower1
if turnOn then lvl = 1 end
-luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
+ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
end
else
-- Handle as Dimming1
-luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
+ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
end
end
@@ -349,19 +349,22 @@ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimme
end
-- Turn off a light, if any is on. Returns 1 if there are more lights to turn off; otherwise 0.
- local function turnOffLight()
- local on
+ local function turnOffLight(on)
local n
- on, n = getTargetsOn()
+ if on == nil then
+ on, n = getTargetsOn()
+ else
+ n = table.getn(on)
+ end
if (n > 0) then
local i = math.random(1, n)
- targetControl(on[i], false)
- luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(on[i]) .. " OFF, " .. tostring(n-1) .. " targets still on.")
- if (n > 1) then
- return true -- there are still lights to turn off
- end
+ local target = on[i]
+ targetControl(target, false)
+ table.remove(on, i)
+ n = n - 1
+ luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(target) .. " OFF, " .. tostring(n) .. " targets still on.")
end
- return false -- no more lights to turn off
+ return (n > 0), on, n
end
-- Turn off all lights as fast as we can. Transition through SHUTDOWN state during,
@@ -590,8 +593,8 @@ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimme
if (s ~= nil) then
if (s) then
-- Turn something off.
+ luup.log("DeusExMachinaII::deusStep(): turn " .. devspec .. " OFF")
targetControl(devspec, false)
- luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to OFF")
else
-- Turn something on. If we're at the max number of targets we're allowed to turn on,
-- turn targets off first.
@@ -601,12 +604,13 @@ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimme
local on, n
on, n = getTargetsOn()
while ( n >= maxOn ) do
- turnOffLight()
- on, n = getTargetsOn()
+ luup.log("DeusExMachinaII:deusStep(): too many targets on, max is " .. tostring(maxOn)
+ .. ", have " .. tostring(n) .. ", let's turn one off.")
+ _, on, n = turnOffLight(on)
end
end
+ luup.log("DeusExMachinaII::deusStep(): turn " .. devspec .. " ON")
targetControl(devspec, true)
- luup.log("DeusExMachinaII::deusStep(): set " .. devspec .. " to ON")
end
end
end
From aeb8f98be1555423b46c8337a974d363625fbcb1 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 17 Dec 2016 10:01:43 -0500
Subject: [PATCH 15/49] Cleaner handling of device type detection. Attempt to
remove targets from Devices list if they're no longer around.
---
I_DeusExMachinaII1.xml | 78 ++++++++++++++++++++++++++----------------
1 file changed, 49 insertions(+), 29 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 56a3e70..35aee6c 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -9,12 +9,13 @@
-- -------------------------------------------------------------------------------------------------------------------------
SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
+ DEMVERSION = 20400
+
SWITCH_TYPE = "urn:schemas-upnp-org:device:BinaryLight:1"
SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
- DEMVERSION = 20400
-
+
STATE_STANDBY = 0
STATE_IDLE = 1
STATE_CYCLE = 2
@@ -28,7 +29,7 @@
luup.variable_set(SID, "UI7Check", "false", lul_device)
ui7Check = "false"
end
- if( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false") then
+ if ( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false" ) then
luup.variable_set(SID, "UI7Check", "true", lul_device)
luup.attr_set("device_json", "D_DeusExMachinaII1_UI7.json", lul_device)
luup.reload()
@@ -194,6 +195,18 @@
until k > string.len(s)
return t, n
end
+
+ local function join(sep, t)
+ local i
+ if (table.getn(t) < 1) then
+ return ""
+ end
+ local s = tostring(t[1])
+ for i=2,table.getn(t) do
+ s = s .. sep .. tostring(t[i])
+ end
+ return s
+ end
-- Return true if a specified scene has been run (i.e. on the list)
local function isSceneOn(spec)
@@ -237,6 +250,20 @@
local s = luup.variable_get(SID, "Devices", lul_device) or ""
return split(s)
end
+
+ -- Remove a target from the target list. Used when the target no longer exists. Linear, poor, but short list and rarely used.
+ local function removeTarget(target, tlist)
+ if tlist == nil then tlist = getTargetList() end
+ local i
+ for i = 1,table.getn(tlist) do
+ if tostring(target) == tlist[i] then
+ table.remove(tlist, i)
+ luup.variable_set(SID, "Devices", join(",", tlist), lul_device)
+ return true
+ end
+ end
+ return false
+ end
-- Light on or off? Returns boolean
local function isDeviceOn(targetid)
@@ -252,20 +279,14 @@
-- luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
local val = "0"
if (luup.devices[r] ~= nil) then
- local t = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
- if (t == nil) then
- t = luup.variable_get(SWITCH_SID, "Status", r)
- if (t == nil) then
- luup.log("DeusExMachinaII::isDeviceOn(): target " .. tostring(targetid) .. " unknown device type")
- return nil
- else
- val = luup.variable_get(SWITCH_SID, "Status", r)
- end
- else
+ if luup.device_supports_service(DIMMER_SID) then
val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
+ elseif luup.device_supports_service(SWITCH_SID) then
+ val = luup.variable_get(SWITCH_SID, "Status", r)
end
else
luup.log("DeusExMachinaII::isDeviceOn(): target spec " .. tostring(targetid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
+ removeTarget(targetid)
return false
end
return val ~= "0"
@@ -292,10 +313,11 @@ luup.log("DeusExMachinaII::targetControl(): handling scene spec " .. targetid)
end
luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
local targetScene
- if (turnOn) then targetScene = onScene else targetScene = offScene end
+ if turnOn then targetScene = onScene else targetScene = offScene end
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
updateSceneState(targetid, turnOn)
else
+ -- Parse the level if this is a dimming target spec
local lvl = 100
local k = string.find(targetid, '=')
if k ~= nil then
@@ -307,24 +329,22 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
if not turnOn then lvl = 0 end
-- luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(targetid) .. ", level " .. tostring(lvl))
if luup.devices[targetid] == nil then
+ -- Device doesn't exist (user deleted, etc.). PHR??? Should remove from Devices state variable.
luup.log("DeusExMachinaII::targetControl(): device " .. tostring(targetid) .. " not found in luup.devices");
+ removeTarget(targetid)
return
end
- local t = luup.variable_get(DIMMER_SID, "LoadLevelTarget", targetid)
- if (t == nil) then
- t = luup.variable_get(SWITCH_SID, "Status", targetid)
- if (t == nil) then
- luup.log("DeusExMachinaII: targetControl(): can't determine device type of devspec=" .. targetid)
- else
- -- Handle as SwitchPower1
- if turnOn then lvl = 1 end
- luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
- luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
- end
- else
+ if luup.device_supports_service(DIMMER_SID) then
-- Handle as Dimming1
luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
+ elseif luup.device_supports_service(SWITCH_SID) then
+ -- Handle as SwitchPower1
+ if turnOn then lvl = 1 end
+ luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
+ luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
+ else
+ luup.log("DeusExMachinaII: targetControl(): don't know how to control target " .. targetid)
end
end
end
@@ -460,12 +480,12 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
- -- Enable DEM by setting a new cycle stamp and calling an initial cycle directly.
+ -- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
function deusEnable()
luup.log("DeusExMachinaII::deusEnable(): enabling...")
luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
runStamp = os.time()
- deusStep(runStamp)
+ luup.call_delay("deusStep", 1, runStamp, 1)
end
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
@@ -486,7 +506,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- One-time stuff
runOnce()
- --check UI version
+ -- Check UI version
checkVersion()
-- Start up if we're enabled
From 2aa890e42c6e6c945ef460b9f8f0b859dc6eeb7d Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 18 Dec 2016 09:51:21 -0500
Subject: [PATCH 16/49] I_DeusExMachinaII1.xml
---
D_DeusExMachinaII1.xml | 10 +++++
D_DeusExMachinaII1_UI7.json | 88 ++++++++++++++++++-------------------
S_DeusExMachinaII1.xml | 2 +-
3 files changed, 55 insertions(+), 45 deletions(-)
diff --git a/D_DeusExMachinaII1.xml b/D_DeusExMachinaII1.xml
index 988efed..db87869 100644
--- a/D_DeusExMachinaII1.xml
+++ b/D_DeusExMachinaII1.xml
@@ -13,6 +13,16 @@
urn:toggledbits-com:serviceId:DeusExMachinaII1
S_DeusExMachinaII1.xml
+
+ urn:schemas-upnp-org:service:SwitchPower:1
+ urn:upnp-org:serviceId:SwitchPower1
+ S_SwitchPower1.xml
+
+
+ urn:schemas-micasaverde-com:service:HaDevice:1
+ urn:micasaverde-com:serviceId:HaDevice1
+ S_HaDevice1.xml
+
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index bf2f81e..36f6bdf 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -10,8 +10,8 @@
"state_icons": [{
"img": "https://dtabq7xg0g1t1.cloudfront.net/deus-red.png",
"conditions": [{
- "service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "variable": "Enabled",
+ "service": "urn:upnp-org:serviceId:SwitchPower1",
+ "variable": "Status",
"operator": "==",
"value": 0,
"subcategory_num": 0
@@ -19,8 +19,8 @@
}, {
"img": "https://dtabq7xg0g1t1.cloudfront.net/deus-green.png",
"conditions": [{
- "service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "variable": "Enabled",
+ "service": "urn:upnp-org:serviceId:SwitchPower1",
+ "variable": "Status",
"operator": "==",
"value": 1,
"subcategory_num": 0
@@ -57,44 +57,44 @@
"left": "1",
"states": [{
"Label": {
- "lang_tag": "dem_enable",
- "text": "Enabled"
+ "lang_tag": "ui7_cmd_on",
+ "text": "On"
},
"ControlGroup": 1,
"Display": {
- "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "Variable": "Enabled",
+ "Service": "urn:upnp-org:serviceId:SwitchPower1",
+ "Variable": "Status",
"Value": "1"
},
"Command": {
- "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "Action": "SetEnabled",
+ "Service": "urn:upnp-org:serviceId:SwitchPower1",
+ "Action": "SetTarget",
"Parameters": [{
- "Name": "NewEnabledValue",
+ "Name": "newTargetValue",
"Value": "1"
}]
},
- "ControlCode": "dem_enable"
+ "ControlCode": "power_on"
}, {
"Label": {
- "lang_tag": "dem_disable",
- "text": "Disabled"
+ "lang_tag": "ui7_cmd_off",
+ "text": "Off"
},
"ControlGroup": 1,
"Display": {
- "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "Variable": "Enabled",
+ "Service": "urn:upnp-org:serviceId:SwitchPower1",
+ "Variable": "Status",
"Value": "0"
},
"Command": {
- "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "Action": "SetEnabled",
+ "Service": "urn:upnp-org:serviceId:SwitchPower1",
+ "Action": "SetTarget",
"Parameters": [{
- "Name": "NewEnabledValue",
+ "Name": "newTargetValue",
"Value": "0"
}]
},
- "ControlCode": "dem_disable"
+ "ControlCode": "power_off"
}],
"ControlCode": "dem_statecontrol"
}, {
@@ -103,7 +103,7 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4RC2 2016-12-15 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks of use without limitation. ."
+ "text": "DeusExMachina II ver 2.4RC3 2016-12-17 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation.."
},
"Display": {
"Top": "80",
@@ -117,16 +117,16 @@
"lang_tag": "configure",
"text": "Configure"
},
- "Position": "2",
+ "Position": "1",
"TabType": "javascript",
"ScriptName": "J_DeusExMachinaII1_UI7.js",
"Function": "DeusExMachinaII.configureDeus"
}, {
"Label": {
- "lang_tag": "advanced",
+ "lang_tag": "ui7_advanced",
"text": "Advanced"
},
- "Position": "3",
+ "Position": "2",
"TabType": "javascript",
"ScriptName": "shared.js",
"Function": "advanced_device"
@@ -135,27 +135,27 @@
"group_1": {
"cmd_1": {
"label": "ON",
- "serviceId": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "action": "SetEnabled",
+ "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
+ "action": "SetTarget",
"arguments": {
- "NewEnabledValue": "1"
+ "newTargetValue": "1"
},
"display": {
- "service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "variable": "Enabled",
+ "service": "urn:upnp-org:serviceId:SwitchPower1",
+ "variable": "Status",
"value": "1"
}
},
"cmd_2": {
"label": "OFF",
- "serviceId": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "action": "SetEnabled",
+ "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
+ "action": "SetTarget",
"arguments": {
- "NewEnabledValue": "0"
+ "newTargetValue": "0"
},
"display": {
- "service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "variable": "Enabled",
+ "service": "urn:upnp-org:serviceId:SwitchPower1",
+ "variable": "Status",
"value": "0"
}
}
@@ -165,27 +165,27 @@
"id": 1,
"label": {
"lang_tag": "dem_enabledisable",
- "text": "The device is enabled or disabled"
+ "text": "Is enabled or disabled"
},
- "serviceId": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
+ "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
"argumentList": [{
"id": 1,
"dataType": "boolean",
"defaultValue": "0",
"allowedValueList": [{
- "value": "0",
+ "Disabled": "0",
"HumanFriendlyText": {
"lang_tag": "dem_disabled",
"text": "_DEVICE_NAME_ is disabled"
}
}, {
- "value": "1",
+ "Enabled": "1",
"HumanFriendlyText": {
"lang_tag": "dem_enabled",
"text": "_DEVICE_NAME_ is enabled"
}
}],
- "name": "Enabled",
+ "name": "Status",
"comparisson": "=",
"prefix": {
"lang_tag": "dem_when",
@@ -208,32 +208,32 @@
"value": "0",
"HumanFriendlyText": {
"lang_tag": "dem_standby",
- "text": "_DEVICE_NAME_ mode is standby"
+ "text": "Standby"
}
}, {
"value": "1",
"HumanFriendlyText": {
"lang_tag": "dem_ready",
- "text": "_DEVICE_NAME_ mode is ready"
+ "text": "Ready"
}
}, {
"value": "2",
"HumanFriendlyText": {
"lang_tag": "dem_cycle",
- "text": "_DEVICE_NAME_ mode is cycling"
+ "text": "Cycling"
}
}, {
"value": "3",
"HumanFriendlyText": {
"lang_tag": "dem_shutoff",
- "text": "_DEVICE_NAME_ mode is shut-off"
+ "text": "Shut-off"
}
}],
"name": "State",
"comparisson": "=",
"prefix": {
"lang_tag": "dem_when",
- "text": "When"
+ "text": "to"
},
"suffix": {}
}]
diff --git a/S_DeusExMachinaII1.xml b/S_DeusExMachinaII1.xml
index 2125a2d..9a8b1de 100644
--- a/S_DeusExMachinaII1.xml
+++ b/S_DeusExMachinaII1.xml
@@ -46,4 +46,4 @@
-
+
\ No newline at end of file
From 2f85ca61487211d31da4afef3a8f3f17ef1922f4 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 18 Dec 2016 09:51:32 -0500
Subject: [PATCH 17/49] First pass at SwitchPower1 SetTarget as focus for
enable/disable
---
I_DeusExMachinaII1.xml | 63 ++++++++++++++++++++++++++----------------
1 file changed, 39 insertions(+), 24 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 35aee6c..4a7224e 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -57,9 +57,11 @@
-- luup.log("DeusExMachinaII::deleteVar(" .. name .. "): status=" .. tostring(status) .. ", result=" .. tostring(result))
end
- -- Shortcut function to return state of Enabled variable
+ -- Shortcut function to return state of SwitchPower1 Status variable
local function isEnabled()
- return ( getVarNumeric("Enabled", 0) ~= 0 )
+ local s = luup.variable_get(SWITCH_SID, "Status", lul_device)
+ if (s == nil or s == "") then return false end
+ return (s ~= "0")
end
local function isActiveHouseMode()
@@ -196,18 +198,6 @@
return t, n
end
- local function join(sep, t)
- local i
- if (table.getn(t) < 1) then
- return ""
- end
- local s = tostring(t[1])
- for i=2,table.getn(t) do
- s = s .. sep .. tostring(t[i])
- end
- return s
- end
-
-- Return true if a specified scene has been run (i.e. on the list)
local function isSceneOn(spec)
local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
@@ -258,7 +248,7 @@
for i = 1,table.getn(tlist) do
if tostring(target) == tlist[i] then
table.remove(tlist, i)
- luup.variable_set(SID, "Devices", join(",", tlist), lul_device)
+ luup.variable_set(SID, "Devices", table.concat(tlist, ","), lul_device)
return true
end
end
@@ -405,11 +395,12 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- takes place. For us, that means looking to see if an older version of Deus is still
-- installed, and copying its config into our new config. Then disable the old Deus.
local function runOnce()
- local s = luup.variable_get(SID, "Enabled", lul_device)
+ local s = luup.variable_get(SID, "Devices", lul_device)
if (s == nil) then
- luup.log("DeusExMachinaII::runOnce(): Enabled variable not found, running...")
+ luup.log("DeusExMachinaII::runOnce(): Devices variable not found, setting up new instance...")
-- See if there are variables from older version of DEM
-- Start by finding the old Deus device, if there is one...
+ local devList = ""
local i, olddev
olddev = -1
for i,v in pairs(luup.devices) do
@@ -439,14 +430,14 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
table.insert(t, s)
end
end
- s = table.concat(t, ",")
- luup.variable_set(SID, "Devices", s, lul_device)
+ devList = table.concat(t, ",")
deleteVar("controlCount", lul_device)
end
-- Finally, turn off old Deus
luup.call_action(oldsid, "SetEnabled", { NewEnabledValue = "0" }, olddev)
end
+ luup.variable_set(SID, "Devices", devList, lul_device)
-- Set up some other default config
luup.variable_set(SID, "MinCycleDelay", "300", lul_device)
@@ -455,8 +446,10 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
luup.variable_set(SID, "MaxOffDelay", "300", lul_device)
luup.variable_set(SID, "LightsOut", 1439, lul_device)
luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
- luup.variable_set(SID, "Enabled", "0", lul_device)
+ luup.variable_set(SID, "Enabled", "0", lul_device, true)
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "0", lul_device, true)
+ luup.variable_set(SWITCH_SID, "Target", "0", lul_device, true)
end
-- Consider per-version changes.
@@ -475,6 +468,12 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
end
deleteVar("LightsOutTime", lul_device)
end
+ if (s < 20400) then
+ luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
+ local e = getVarNumeric("Enabled", 0)
+ luup.variable_set(SWITCH_SID, "Status", e, lul_device, true)
+ luup.variable_set(SWITCH_SID, "Target", e, lul_device, true)
+ end
-- Update version last.
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
@@ -486,6 +485,8 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
runStamp = os.time()
luup.call_delay("deusStep", 1, runStamp, 1)
+ luup.variable_set(SID, "Enabled", 1, lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "1", lul_device)
end
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
@@ -497,11 +498,13 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
clearLights()
end
luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
+ luup.variable_set(SID, "Enabled", 0, lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
end
-- Initialize.
function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.4RC2 (2016-12-15), starting up...")
+ luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-17), initializing...")
-- One-time stuff
runOnce()
@@ -651,16 +654,28 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
deusInit
+
+ urn:upnp-org:serviceId:SwitchPower1
+ SetTarget
+
+ local newTargetValue = lul_settings.newTargetValue or "0"
+ luup.variable_set(SWITCH_SID, "Target", newTargetValue, lul_device)
+ if (newTargetValue == "1") then
+ deusEnable()
+ else
+ deusDisable()
+ end
+
+
urn:toggledbits-com:serviceId:DeusExMachinaII1
SetEnabled
- local newEnabledValue = lul_settings.NewEnabledValue
+ local newEnabledValue = lul_settings.NewEnabledValue or "0"
+ luup.variable_set(SWITCH_SID, "Target", newEnabledValue, lul_device)
if (newEnabledValue == "1") then
- luup.variable_set(SID, "Enabled", 1, lul_device)
deusEnable()
else
- luup.variable_set(SID, "Enabled", 0, lul_device)
deusDisable()
end
From 670854bf0e81eea90e512aef21639a5798ad3bf2 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 18 Dec 2016 09:51:56 -0500
Subject: [PATCH 18/49] First pass at SwitchPower1 SetTarget as focus for
enable/disable
---
README.md | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 2b66910..13750f2 100644
--- a/README.md
+++ b/README.md
@@ -80,22 +80,23 @@ Finally, 2.4 adds the ability for a "final scene" to run when DEMII is disabled
As of version 2.0 and on UI7, DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
-A Lua interface is also supported since version 1.1 for both UI5 and UI7, via a luup.call_action() call:
+A Lua interface is also supported since version 1.1 for both UI5 and UI7. All versions of support the SetEnabled action, although for DEMII versions 2.4 and higher, the use of SetTarget in the standard SwitchPower1 service is preferred. Examples (the "0|1" means use either 0 or 1 to disable or enable, respectively):
```
--- For the new Deus Ex Machina II plugin (v2.0 and higher), do this:
+-- Preferred for DEMII versions 2.4 and higher:
+luup.call_action("urn:upnp-org-serviceId:SwitchPower1", "SetTarget", { newTargetValue = "0|1" }, deviceID)
+
+-- Also works for all versions of DEMII:
luup.call_action("urn:toggledbits-com:serviceId:DeusExMachinaII1", "SetEnabled", { NewEnabledValue = "0|1" }, deviceID)
-- For the old Deus Ex Machina plugin (v1.1 and earlier) running on UI5 or UI7, do this:
luup.call_action("urn:futzle-com:serviceId:DeusExMachina1", "SetEnabled", { NewEnabledValue = "0|1" }, deviceID)
```
-Of course, only one of either "0" or "1" should be specified.
-
-Note that when disabling Deus Ex Machina from a scene or the user interface, versions 1.1 and 2.0 operate differently. Version 1.1 will simply stop cycling lights, leaving on any controlled lights it may have turned on.
-Version 2, however, will turn off all controlled lights _if it was in the cycling period (between sunset and lights out time) at the time it was disabled_.
+Note that when disabling Deus Ex Machina from a scene or the user interface, versions 1.1 and 2.x (DEMII) operate differently. Version 1.1 will simply stop cycling lights, leaving on any controlled lights it may have turned on.
+DEMII, however, will turn off all controlled lights _if it was in the cycling period (between sunset and lights out time) at the time it was disabled_.
-Version 2.0 also added the ability for a change of DeusExMachina's Enabled state to be used as trigger in scenes and other places where events can be watched (e.g. Program Logic plugins, etc.). This also works on UI7 only.
+DEMII version 2.0 also added the ability for a change of DeusExMachina's operating state to be used as trigger in scenes and other places where events can be watched (e.g. Program Logic plugins, etc.). This also works on UI7 only.
#### Triggers ####
@@ -107,17 +108,18 @@ For the "operating mode changes" event, the trigger fires when DEMII's operating
* Standby - DEMII is disabled (this is equivalent to the "device is disabled" state event);
-* Ready - DEMII is enabled and waiting for the next sunset;
+* Ready - DEMII is enabled and waiting for the next sunset (and house mode, if applicable);
-* Cycling - DEMII is enabled and cycling lights in the active period after sunset and before the "lights out" time;
+* Cycling - DEMII is cycling lights, that is, it is enabled, in the period between sunset and the set "lights out" time, and correct house mode (if applicable);
* Shut-off - DEMII is enabled and shutting off lights, having reached the "lights out" time.
-When disabled, DEMII is always in Standby mode. When enabled, DEMII enters the Ready mode, then transitions to Cycling mode at sunset, then Shut-off mode at the "lights out" time, and then when all lights have
-been shut off, returns to the Ready mode waiting for the next day's sunset. The transition between Ready, Cycling, and Shut-off continues until DEMII is disabled (at which point it goes to Standby).
+When disabled, DEMII is always in Standby mode. When enabled, DEMII enters the Ready mode, then transitions to Cycling mode at sunset, then Shut-off mode at the "lights out" time,
+and then when all lights have been shut off, returns to the Ready mode waiting for the next day's sunset. The transition between Ready, Cycling, and Shut-off continues until DEMII
+is disabled (at which point it goes to Standby).
-It should be noted that DEMII can enter Cycling or Shut-off mode immediately, without passing through Ready, if it is enabled after sunset or after the "lights out" time, respectively.
-DEMII will also transition into or out of Standby mode immediately and from any other mode when disabled or enabled, respectively.
+It should be noted that DEMII can enter Cycling or Shut-off mode immediately, without passing through Ready, if it is enabled after sunset or after the "lights out" time,
+respectively. DEMII will also transition into or out of Standby mode immediately and from any other mode when disabled or enabled, respectively.
#### Cycle Timing ####
From 05d87e08872fa4b368ebc34d43b8a5a38b82abcf Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 18 Dec 2016 20:26:18 -0500
Subject: [PATCH 19/49] Fix minor broken-ness. Whoa.
---
I_DeusExMachinaII1.xml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 4a7224e..6d2dcd6 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -269,9 +269,9 @@
-- luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
local val = "0"
if (luup.devices[r] ~= nil) then
- if luup.device_supports_service(DIMMER_SID) then
+ if luup.device_supports_service(DIMMER_SID, r) then
val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
- elseif luup.device_supports_service(SWITCH_SID) then
+ elseif luup.device_supports_service(SWITCH_SID, r) then
val = luup.variable_get(SWITCH_SID, "Status", r)
end
else
@@ -324,11 +324,11 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
removeTarget(targetid)
return
end
- if luup.device_supports_service(DIMMER_SID) then
+ if luup.device_supports_service(DIMMER_SID, targetid) then
-- Handle as Dimming1
luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
- elseif luup.device_supports_service(SWITCH_SID) then
+ elseif luup.device_supports_service(SWITCH_SID, targetid) then
-- Handle as SwitchPower1
if turnOn then lvl = 1 end
luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
From 7a9c603ed951d1ef440323dc94908394c81551d1 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Wed, 21 Dec 2016 10:56:51 -0500
Subject: [PATCH 20/49] A little more debug checking house mode. Clean up
delays in cycling loop; some places I was using OffDelay where I should have
used CycleDelay (not harmful, just makes Vera test things a bit more often
than generally necessary).
---
I_DeusExMachinaII1.xml | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 6d2dcd6..75e66a5 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -87,6 +87,8 @@
if (bit.band(modebits, currentMode) == 0) then
luup.log('DeusExMachinaII::isActiveHouseMode(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' not in set ' .. string.format("0x%x", modebits))
return false -- not active in this mode
+ else
+ luup.log('DeusExMachinaII::isActiveHouseMode(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' SET in ' .. string.format("0x%x", modebits))
end
end
return true -- default is we're active in the current house mode
@@ -277,7 +279,7 @@
else
luup.log("DeusExMachinaII::isDeviceOn(): target spec " .. tostring(targetid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
removeTarget(targetid)
- return false
+ return nil
end
return val ~= "0"
end
@@ -485,7 +487,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
runStamp = os.time()
luup.call_delay("deusStep", 1, runStamp, 1)
- luup.variable_set(SID, "Enabled", 1, lul_device)
+ luup.variable_set(SID, "Enabled", "1", lul_device)
luup.variable_set(SWITCH_SID, "Status", "1", lul_device)
end
@@ -498,7 +500,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
clearLights()
end
luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
- luup.variable_set(SID, "Enabled", 0, lul_device)
+ luup.variable_set(SID, "Enabled", "0", lul_device)
luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
end
@@ -570,7 +572,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
-- Go to IDLE and delay for next sunset.
luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...");
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
elseif (not isActiveHouseMode()) then
-- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
@@ -585,9 +587,9 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
-- to re-check house mode, which could change at any time, so we must deal with it.
if (inActiveTimePeriod) then
- nextCycleDelay = 300
+ nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
else
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
end
elseif (not inActiveTimePeriod) then
luup.log("DeusExMachinaII::deusStep(): running off cycle")
@@ -596,7 +598,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- No more lights to turn off
runFinalScene()
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
luup.log("DeusExMachina::deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
else
nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
@@ -615,7 +617,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
local s = isDeviceOn(devspec)
if (s ~= nil) then
if (s) then
- -- Turn something off.
+ -- It's on; turn it off.
luup.log("DeusExMachinaII::deusStep(): turn " .. devspec .. " OFF")
targetControl(devspec, false)
else
From b16f80c9b84749b46196bbaf42d2b4da4de9bae7 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Wed, 21 Dec 2016 10:59:24 -0500
Subject: [PATCH 21/49] Update RC3 date
---
D_DeusExMachinaII1_UI7.json | 2 +-
I_DeusExMachinaII1.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index 36f6bdf..63784c5 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -103,7 +103,7 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4RC3 2016-12-17 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation.."
+ "text": "DeusExMachina II ver 2.4RC3 2016-12-21 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation.."
},
"Display": {
"Top": "80",
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 75e66a5..b0b8a5b 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -506,7 +506,7 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
-- Initialize.
function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-17), initializing...")
+ luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-21), initializing...")
-- One-time stuff
runOnce()
From c9a3a97f1bab57b9dbaa3f216f4657c7c95979a7 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 23 Dec 2016 13:13:02 -0500
Subject: [PATCH 22/49] Move most code off to module
---
I_DeusExMachinaII1.xml | 664 +----------------------------------------
L_DeusExMachinaII1.lua | 649 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 661 insertions(+), 652 deletions(-)
create mode 100644 L_DeusExMachinaII1.lua
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index b0b8a5b..d647e1d 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -7,665 +7,25 @@
-- Original code and releases 1.x by Andy Lintner (beowulfe) Version 2.0 and beyond by Patrick Rigney (rigpapa/toggledbits).
-- A big thanks to Andy for passing the torch so that this great plug-in can live on.
-- -------------------------------------------------------------------------------------------------------------------------
-
- SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
- DEMVERSION = 20400
-
- SWITCH_TYPE = "urn:schemas-upnp-org:device:BinaryLight:1"
- SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
- DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
- DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
-
- STATE_STANDBY = 0
- STATE_IDLE = 1
- STATE_CYCLE = 2
- STATE_SHUTDOWN = 3
-
- runStamp = 0
-
- local function checkVersion()
- local ui7Check = luup.variable_get(SID, "UI7Check", lul_device) or ""
- if ui7Check == "" then
- luup.variable_set(SID, "UI7Check", "false", lul_device)
- ui7Check = "false"
- end
- if ( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false" ) then
- luup.variable_set(SID, "UI7Check", "true", lul_device)
- luup.attr_set("device_json", "D_DeusExMachinaII1_UI7.json", lul_device)
- luup.reload()
- end
- end
-
- -- Get numeric variable, or return default value if not set or blank
- local function getVarNumeric( name, dflt )
- local s = luup.variable_get(SID, name, lul_device)
- if (s == nil or s == "") then return dflt end
- s = tonumber(s, 10)
- if (s == nil) then return dflt end
- return s
- end
-
- -- Delete a variable (if we can... read on...)
- local function deleteVar( name, devid )
- if (devid == nil) then devid = luup.device end
- -- Interestingly, setting a variable to nil with luup.variable_set does nothing interesting; too bad, it
- -- could have been used to delete variables, since a later get would yield nil anyway. But it turns out
- -- that using the variableget Luup request with no value WILL delete the variable.
- local req = "http://127.0.0.1:3480/data_request?id=variableset&DeviceNum=" .. tostring(devid) .. "&serviceId=" .. SID .. "&Variable=" .. name .. "&Value="
- -- luup.log("DeusExMachinaII::deleteVar(" .. name .. "): req=" .. tostring(req))
- local status, result = luup.inet.wget(req)
- -- luup.log("DeusExMachinaII::deleteVar(" .. name .. "): status=" .. tostring(status) .. ", result=" .. tostring(result))
- end
-
- -- Shortcut function to return state of SwitchPower1 Status variable
- local function isEnabled()
- local s = luup.variable_get(SWITCH_SID, "Status", lul_device)
- if (s == nil or s == "") then return false end
- return (s ~= "0")
- end
-
- local function isActiveHouseMode()
- -- Fetch our mask bits that tell us what modes we operate in. If 0, we're not checking house mode.
- local modebits = getVarNumeric("HouseModes", 0)
- if (modebits ~= 0) then
- -- Get the current house mode. There seems to be some disharmony in the correct way to go
- -- about this, but the method (uncommented) below works.
- -- local currentMode = luup.attr_get("Mode") -- alternate method
- local currentMode
- local status
- status, currentMode = luup.inet.wget("http://127.0.0.1:3480/data_request?id=variableget&Variable=Mode", 2)
- if status ~= 0 then
- luup.log("DeusExMachinaII::isActiveHouseMode(): can't get current house mode, status=" .. tostring(status))
- currentMode = 0
- end
-
- -- Check to see if house mode bits are non-zero, and if so, apply current mode as mask.
- -- If bit is set (current mode is in the bitset), we can run, otherwise skip.
- local bit = require("bit")
- -- Get the current house mode (1=Home,2=Away,3=Night,4=Vacation)
- currentMode = math.pow(2, tonumber(currentMode,10))
- if (bit.band(modebits, currentMode) == 0) then
- luup.log('DeusExMachinaII::isActiveHouseMode(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' not in set ' .. string.format("0x%x", modebits))
- return false -- not active in this mode
- else
- luup.log('DeusExMachinaII::isActiveHouseMode(): Current mode bit ' .. string.format("0x%x", currentMode) .. ' SET in ' .. string.format("0x%x", modebits))
- end
- end
- return true -- default is we're active in the current house mode
- end
-
- -- Get a random delay from two state variables. Error check.
- function getRandomDelay(minStateName,maxStateName,defMin,defMax)
- if defMin == nil then defMin = 300 end
- if defMax == nil then defMax = 1800 end
- local mind = getVarNumeric(minStateName, defMin)
- if mind < 1 then mind = 1 elseif mind > 7200 then mind = 7200 end
- local maxd = getVarNumeric(maxStateName, defMax)
- if maxd < 1 then maxd = 1 elseif maxd > 7200 then maxd = 7200 end
- if maxd < mind then maxd = mind end
- return math.random( mind, maxd )
- end
-
- -- Get sunset time in minutes since midnight. May override for test mode value.
- function getSunset()
- -- Figure out our sunset time. Note that if we make this inquiry after sunset, MiOS
- -- returns the time of tomorrow's sunset. But, that's not different enough from today's
- -- that it really matters to us, so go with it.
- local sunset = luup.sunset()
- local testing = getVarNumeric("TestMode", 0)
- if (testing ~= 0) then
- local m = getVarNumeric( "TestSunset", nil ) -- units are minutes since midnight
- if (m ~= nil) then
- -- Sub in our test sunset time
- local t = os.date('*t', sunset)
- t['hour'] = math.floor(m / 60)
- t['min'] = math.floor(m % 60)
- t['sec'] = 0
- sunset = os.time(t)
- end
- luup.log('DeusExMachinaII::getSunset(): testing mode sunset override ' .. tostring(m) .. ', as time is ' .. tostring(sunset))
- end
- if (sunset <= os.time()) then sunset = sunset + 86400 end
- return sunset
- end
-
- function getSunsetMSM()
- local t = os.date('*t', getSunset());
- return t['hour']*60 + t['min']
- end
-
- -- DEM cycles lights between sunset and the user-specified off time. This function returns 0
- -- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
- -- to minutes-since-midnight units.
- local function isBedtime()
- local testing = getVarNumeric("TestMode", 0)
- if (testing ~= 0) then luup.log('DeusExMachinaII::isBedtime(): TestMode is on') end
-
- -- Establish the lights-out time
- local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
- local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
- if (bedtime_tmp ~= nil) then
- bedtime_tmp = tonumber(bedtime_tmp,10)
- if (bedtime_tmp >= 0 and bedtime_tmp < 1440) then bedtime = bedtime_tmp end
- end
-
- -- Figure out our sunset time.
- local sunset = getSunsetMSM()
-
- -- And the current time.
- local date = os.date('*t')
- local time = date['hour'] * 60 + date['min']
-
- -- Figure out if we're betweeen sunset and lightout (ret=0) or not (ret=1)
- if (testing ~= 0) then
- luup.log('DeusExMachinaII::isBedtime(): times (mins since midnight) are now=' .. tostring(time) .. ', sunset=' .. tostring(sunset) .. ', bedtime=' .. tostring(bedtime))
- end
- local ret = 1 -- guilty until proven innocent
- if (bedtime > sunset) then
- -- Case 1: bedtime is after sunset (i.e. between sunset and midnight)
- if (time >= sunset and time < bedtime) then
- ret = 0
- end
- else
- -- Case 2: bedtime is after midnight
- if (time >= sunset or time < bedtime) then
- ret = 0
- end
- end
- if (testing ~= 0) then luup.log("DeusExMachinaII::isBedtime(): returning " .. tostring(ret)) end
- return ret
- end
-
- -- Take a string and split it around sep, returning table (indexed) of substrings
- -- For example abc,def,ghi becomes t[1]=abc, t[2]=def, t[3]=ghi
- -- Returns: table of values, count of values (integer ge 0)
- local function split(s, sep)
- local t = {}
- local n = 0
- if (#s == 0) then return t,n end -- empty string returns nothing
- local i,j
- local k = 1
- repeat
- i, j = string.find(s, sep or "%s*,%s*", k)
- if (i == nil) then
- table.insert(t, string.sub(s, k, -1))
- n = n + 1
- break
- else
- table.insert(t, string.sub(s, k, i-1))
- n = n + 1
- k = j + 1
- end
- until k > string.len(s)
- return t, n
- end
-
- -- Return true if a specified scene has been run (i.e. on the list)
- local function isSceneOn(spec)
- local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
- for i in string.gfind(stateList, "[^,]+") do
- if (i == spec) then return true end
- end
- return false
- end
-
- -- Mark or unmark a scene as having been run
- local function updateSceneState(spec, isOn)
- local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
- local i
- local t = {}
- for i in string.gfind(stateList, "[^,]+") do
- t[i] = 1
- end
- if (isOn) then
- t[spec] = 1
- else
- t[spec] = nil
- end
- stateList = ""
- for i in pairs(t) do stateList = stateList .. "," .. tostring(i) end
- luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
- end
-
- -- Run "final" scene, if defined. This scene is run after all other targets have been
- -- turned off.
- local function runFinalScene()
- local scene = getVarNumeric("FinalScene", nil)
- if (scene ~= nil) then
- luup.log("DeusExMachina::runFinalScene(): running final scene " .. tostring(scene))
- luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=scene }, 0)
- end
- end
-
- -- Get the list of targets from our device state, parse to table of targets.
- local function getTargetList()
- local s = luup.variable_get(SID, "Devices", lul_device) or ""
- return split(s)
- end
-
- -- Remove a target from the target list. Used when the target no longer exists. Linear, poor, but short list and rarely used.
- local function removeTarget(target, tlist)
- if tlist == nil then tlist = getTargetList() end
- local i
- for i = 1,table.getn(tlist) do
- if tostring(target) == tlist[i] then
- table.remove(tlist, i)
- luup.variable_set(SID, "Devices", table.concat(tlist, ","), lul_device)
- return true
- end
- end
- return false
- end
-
- -- Light on or off? Returns boolean
- local function isDeviceOn(targetid)
- local first = string.upper(string.sub(targetid, 1, 1))
- if (first == "S") then
- -- luup.log("DeusExMachinaII::isDeviceOn(): handling scene spec " .. targetid)
- return isSceneOn(targetid)
- end
-
- -- Handle as switch or dimmer
- -- luup.log("DeusExMachinaII::isDeviceOn(): handling target spec " .. targetid)
- local r = tonumber(string.match(targetid, '^%d+'), 10)
- -- luup.log("DeusExMachinaII::isDeviceOn(): target number seems to be " .. tostring(r))
- local val = "0"
- if (luup.devices[r] ~= nil) then
- if luup.device_supports_service(DIMMER_SID, r) then
- val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
- elseif luup.device_supports_service(SWITCH_SID, r) then
- val = luup.variable_get(SWITCH_SID, "Status", r)
- end
- else
- luup.log("DeusExMachinaII::isDeviceOn(): target spec " .. tostring(targetid) .. " device " .. tostring(r) .. ", device not found in luup.devices")
- removeTarget(targetid)
- return nil
- end
- return val ~= "0"
- end
-
- -- Control target. Target is a string, expected to be a pure integer (in which case the target is assumed to be a switch or dimmer),
- -- or a string in the form Sxx:yy, in which case xx is an "on" scene to run, and yy is an "off" scene to run.
- local function targetControl(targetid, turnOn)
- luup.log("DeusExMachinaII::targetControl(): targetid=" .. tostring(targetid) .. ", turnOn=" .. tostring(turnOn))
- local first = string.upper(string.sub(targetid, 1, 1))
- if first == "S" then
-luup.log("DeusExMachinaII::targetControl(): handling scene spec " .. targetid)
- i, j, onScene, offScene = string.find(string.sub(targetid, 2), "(%d+)-(%d+)")
- if (i == nil) then
- luup.log("DeusExMachina::targetControl(): malformed scene spec=" .. targetid)
- return
- end
- onScene = tonumber(onScene, 10)
- offScene = tonumber(offScene, 10)
- if luup.scenes[onScene] == nil or luup.scenes[offScene] == nil then
- -- Both on scene and off scene must exist (defensive).
- luup.log("DeusExMachinaII::targetControl(): one or both of the scenes in " .. tostring(targetid) .. " not found in luup.scenes!")
- return
- end
-luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .. ", off scene is " .. tostring(offScene))
- local targetScene
- if turnOn then targetScene = onScene else targetScene = offScene end
- luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
- updateSceneState(targetid, turnOn)
- else
- -- Parse the level if this is a dimming target spec
- local lvl = 100
- local k = string.find(targetid, '=')
- if k ~= nil then
- _, _, targetid, lvl = string.find(targetid, "(%d+)=(%d+)")
- lvl = tonumber(lvl, 10)
- end
- targetid = tonumber(targetid, 10)
- -- Level for all types is 0 if turning device off
- if not turnOn then lvl = 0 end
- -- luup.log("DeusExMachinaII::targetControl(): handling device " .. tostring(targetid) .. ", level " .. tostring(lvl))
- if luup.devices[targetid] == nil then
- -- Device doesn't exist (user deleted, etc.). PHR??? Should remove from Devices state variable.
- luup.log("DeusExMachinaII::targetControl(): device " .. tostring(targetid) .. " not found in luup.devices");
- removeTarget(targetid)
- return
- end
- if luup.device_supports_service(DIMMER_SID, targetid) then
- -- Handle as Dimming1
- luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as dimmer, setting load level to " .. lvl)
- luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
- elseif luup.device_supports_service(SWITCH_SID, targetid) then
- -- Handle as SwitchPower1
- if turnOn then lvl = 1 end
- luup.log("DeusExMachinaII: targetControl(): handling " .. targetid .. " as binary light, setting target to " .. lvl)
- luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
- else
- luup.log("DeusExMachinaII: targetControl(): don't know how to control target " .. targetid)
- end
- end
- end
-
- -- Get list of targets that are on
- local function getTargetsOn()
- local devs, max
- local on = {}
- local n = 0
- devs,max = getTargetList()
- if (max > 0) then
- local i
- for i = 1,max do
- local devOn = isDeviceOn(devs[i])
- if (devOn ~= nil and devOn) then
- table.insert(on, devs[i])
- n = n + 1
- end
- end
- end
- return on,n
- end
-
- -- Turn off a light, if any is on. Returns 1 if there are more lights to turn off; otherwise 0.
- local function turnOffLight(on)
- local n
- if on == nil then
- on, n = getTargetsOn()
- else
- n = table.getn(on)
- end
- if (n > 0) then
- local i = math.random(1, n)
- local target = on[i]
- targetControl(target, false)
- table.remove(on, i)
- n = n - 1
- luup.log("DeusExMachinaII::turnOffLight(): turned " .. tostring(target) .. " OFF, " .. tostring(n) .. " targets still on.")
- end
- return (n > 0), on, n
- end
-
- -- Turn off all lights as fast as we can. Transition through SHUTDOWN state during,
- -- in case user has any triggers connected to that state. The caller must immediately
- -- set the next state when this function returns (expected would be STANDBY or IDLE).
- function clearLights()
- local devs, count
- devs, count = getTargetList()
- luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
- while count > 0 do
- targetControl(devs[count], false)
- count = count - 1
- end
- runFinalScene()
- end
-
- -- runOnce() looks to see if a core state variable exists; if not, a one-time initialization
- -- takes place. For us, that means looking to see if an older version of Deus is still
- -- installed, and copying its config into our new config. Then disable the old Deus.
- local function runOnce()
- local s = luup.variable_get(SID, "Devices", lul_device)
- if (s == nil) then
- luup.log("DeusExMachinaII::runOnce(): Devices variable not found, setting up new instance...")
- -- See if there are variables from older version of DEM
- -- Start by finding the old Deus device, if there is one...
- local devList = ""
- local i, olddev
- olddev = -1
- for i,v in pairs(luup.devices) do
- if (v.device_type == "urn:schemas-futzle-com:device:DeusExMachina:1") then
- luup.log("DeusExMachinaII::runOnce(): Found old Deus Ex Machina device #" .. tostring(i))
- olddev = i
- break
- end
- end
- if (olddev > 0) then
- -- We found an old Deus device, copy its config into our new state variables
- local oldsid = "urn:futzle-com:serviceId:DeusExMachina1"
- s = luup.variable_get(oldsid, "LightsOutTime", olddev)
- if (s ~= nil) then
- local n = tonumber(s,10) / 60000
- luup.variable_set(SID, "LightsOut", n, lul_device)
- deleteVar("LightsOutTime", lul_device)
- end
- s = luup.variable_get(oldsid, "controlCount", olddev)
- if (s ~= nil) then
- local n = tonumber(s, 10)
- local k
- local t = {}
- for k = 1,n do
- s = luup.variable_get(oldsid, "control" .. tostring(k-1), olddev)
- if (s ~= nil) then
- table.insert(t, s)
- end
- end
- devList = table.concat(t, ",")
- deleteVar("controlCount", lul_device)
- end
-
- -- Finally, turn off old Deus
- luup.call_action(oldsid, "SetEnabled", { NewEnabledValue = "0" }, olddev)
- end
- luup.variable_set(SID, "Devices", devList, lul_device)
-
- -- Set up some other default config
- luup.variable_set(SID, "MinCycleDelay", "300", lul_device)
- luup.variable_set(SID, "MaxCycleDelay", "1800", lul_device)
- luup.variable_set(SID, "MinOffDelay", "60", lul_device)
- luup.variable_set(SID, "MaxOffDelay", "300", lul_device)
- luup.variable_set(SID, "LightsOut", 1439, lul_device)
- luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
- luup.variable_set(SID, "Enabled", "0", lul_device, true)
- luup.variable_set(SID, "Version", DEMVERSION, lul_device)
- luup.variable_set(SWITCH_SID, "Status", "0", lul_device, true)
- luup.variable_set(SWITCH_SID, "Target", "0", lul_device, true)
- end
-
- -- Consider per-version changes.
- s = getVarNumeric("Version", 0)
- if (s < 20300) then
- -- v2.3: LightsOutTime (in milliseconds) deprecated, now using LightsOut (in minutes since midnight)
- luup.log("DeusExMachinaII::runOnce(): updating config, version " .. tostring(s) .. " < 20300")
- s = luup.variable_get(SID, "LightsOut", lul_device)
- if (s == nil) then
- s = getVarNumeric("LightsOutTime") -- get pre-2.3 variable
- if (s == nil) then
- luup.variable_set(SID, "LightsOut", 1439, lul_device) -- default 23:59
- else
- luup.variable_set(SID, "LightsOut", tonumber(s,10) / 60000, lul_device) -- conv ms to minutes
- end
- end
- deleteVar("LightsOutTime", lul_device)
- end
- if (s < 20400) then
- luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
- local e = getVarNumeric("Enabled", 0)
- luup.variable_set(SWITCH_SID, "Status", e, lul_device, true)
- luup.variable_set(SWITCH_SID, "Target", e, lul_device, true)
- end
-
- -- Update version last.
- luup.variable_set(SID, "Version", DEMVERSION, lul_device)
- end
-
- -- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
- function deusEnable()
- luup.log("DeusExMachinaII::deusEnable(): enabling...")
- luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
- runStamp = os.time()
- luup.call_delay("deusStep", 1, runStamp, 1)
- luup.variable_set(SID, "Enabled", "1", lul_device)
- luup.variable_set(SWITCH_SID, "Status", "1", lul_device)
- end
-
- -- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
- -- turn off any controlled lights that are on.
- function deusDisable()
- local s = getVarNumeric("State", STATE_STANDBY)
- luup.log("DeusExMachinaII::deusDisable(): disabling...")
- if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
- clearLights()
- end
- luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
- luup.variable_set(SID, "Enabled", "0", lul_device)
- luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
- end
-
- -- Initialize.
- function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-21), initializing...")
-
- -- One-time stuff
- runOnce()
-
- -- Check UI version
- checkVersion()
-
- -- Start up if we're enabled
- if (isEnabled()) then
- deusEnable()
- else
- deusDisable()
- end
- end
-
- -- Run a cycle. If we're in "bedtime" (i.e. not between our cycle period between sunset and stop),
- -- then we'll shut off any lights we've turned on and queue another run for the next sunset. Otherwise,
- -- we'll toggled one of our controlled lights, and queue (random delay, but soon) for another cycle.
- -- The shutdown of lights also occurs randomly, but can (through device state/config) have different
- -- delays, so the lights going off looks more "natural" (i.e. not all at once just slamming off).
- function deusStep(stepStampCheck)
- local stepStamp = tonumber(stepStampCheck)
- luup.log("DeusExMachinaII::deusStep(): wakeup, stamp " .. stepStampCheck)
- if (stepStamp ~= runStamp) then
- luup.log("DeusExMachinaII::deusStep(): stamp mismatch, another thread running. Bye!")
- return
- end
- if (not isEnabled()) then
- luup.log("DeusExMachinaII::deusStep(): not enabled, no more work for this thread...")
- return
- end
-
- -- Get next sunset as seconds since midnight (approx)
- local sunset = getSunset()
-
- local currentState = getVarNumeric("State", 0)
- if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
- luup.log("DeusExMachinaII::deusStep(): run in state "
- .. tostring(currentState)
- .. ", lightsout=" .. tostring(luup.variable_get(SID, "LightsOut", lul_device))
- .. ", sunset=" .. tostring(sunset)
- .. ", os.time=" .. tostring(os.time())
- )
- luup.log("+ long="
- .. tostring(luup.longitude)
- .. ", lat=" .. tostring(luup.latitude)
- .. ", tz=" .. tostring(luup.timezone)
- .. ", city=" .. tostring(luup.city)
- .. ", luup.sunset=" .. tostring(luup.sunset())
- .. ", version=" .. tostring(luup.version)
- )
- end
-
- local inActiveTimePeriod = true
- if (isBedtime() ~= 0) then
- luup.log("DeusExMachinaII::deusStep(): in lights out time")
- inActiveTimePeriod = false
- end
-
- -- Get going...
- local nextCycleDelay = 300 -- a default value to keep us out of hot water
- if (currentState == STATE_STANDBY and not inActiveTimePeriod) then
- -- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
- -- Go to IDLE and delay for next sunset.
- luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...");
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
- elseif (not isActiveHouseMode()) then
- -- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
- if (currentState ~= STATE_IDLE) then
- luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE, not in an active house mode.");
- if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
- else
- luup.log("DeusExMachinaII::deusStep(): IDLE in an inactive house mode; waiting for mode change.");
- end
-
- -- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
- -- to re-check house mode, which could change at any time, so we must deal with it.
- if (inActiveTimePeriod) then
- nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- else
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- end
- elseif (not inActiveTimePeriod) then
- luup.log("DeusExMachinaII::deusStep(): running off cycle")
- luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
- if (not turnOffLight()) then
- -- No more lights to turn off
- runFinalScene()
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- luup.log("DeusExMachina::deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
- else
- nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
- end
- else
- -- Fully active. Find a random target to control and control it.
- luup.log("DeusExMachinaII::deusStep(): running toggle cycle")
- luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
- nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- local devs, max
- devs, max = getTargetList()
- if (max > 0) then
- local change = math.random(1, max)
- local devspec = devs[change]
- if (devspec ~= nil) then
- local s = isDeviceOn(devspec)
- if (s ~= nil) then
- if (s) then
- -- It's on; turn it off.
- luup.log("DeusExMachinaII::deusStep(): turn " .. devspec .. " OFF")
- targetControl(devspec, false)
- else
- -- Turn something on. If we're at the max number of targets we're allowed to turn on,
- -- turn targets off first.
- local maxOn = getVarNumeric("MaxTargetsOn")
- if (maxOn == nil) then maxOn = 0 else maxOn = tonumber(maxOn,10) end
- if (maxOn > 0) then
- local on, n
- on, n = getTargetsOn()
- while ( n >= maxOn ) do
- luup.log("DeusExMachinaII:deusStep(): too many targets on, max is " .. tostring(maxOn)
- .. ", have " .. tostring(n) .. ", let's turn one off.")
- _, on, n = turnOffLight(on)
- end
- end
- luup.log("DeusExMachinaII::deusStep(): turn " .. devspec .. " ON")
- targetControl(devspec, true)
- end
- end
- end
- else
- luup.log("DeusExMachinaII:deusStep(): no targets to control")
- end
- end
-
- -- Arm for next cycle
- if nextCycleDelay ~= nil then
- luup.log("DeusExMachinaII::deusStep(): cycle finished, next in " .. nextCycleDelay .. " seconds")
- if nextCycleDelay < 1 then nextCycleDelay = 60 end
- luup.call_delay("deusStep", nextCycleDelay, stepStamp, 1)
- else
- luup.log("DeusExMachinaII::deusStep(): nil nextCycleDelay, next cycle not scheduled!");
- end
+ function startupDeusExMachinaII1()
+ luup.log("DeusExMachinaII STARTUP!")
+ demII = require("L_DeusExMachinaII1")
+ deusStep = demII.deusStep
+ demII.deusInit()
end
- deusInit
+ startupDeusExMachinaII1
urn:upnp-org:serviceId:SwitchPower1
SetTarget
local newTargetValue = lul_settings.newTargetValue or "0"
- luup.variable_set(SWITCH_SID, "Target", newTargetValue, lul_device)
+ luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newTargetValue, lul_device)
if (newTargetValue == "1") then
- deusEnable()
+ demII.deusEnable()
else
- deusDisable()
+ demII.deusDisable()
end
@@ -674,11 +34,11 @@ luup.log("DeusExMachinaII::targetControl(): on scene is " .. tostring(onScene) .
SetEnabled
local newEnabledValue = lul_settings.NewEnabledValue or "0"
- luup.variable_set(SWITCH_SID, "Target", newEnabledValue, lul_device)
+ luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newEnabledValue, lul_device)
if (newEnabledValue == "1") then
- deusEnable()
+ demII.deusEnable()
else
- deusDisable()
+ demII.deusDisable()
end
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
new file mode 100644
index 0000000..9048e64
--- /dev/null
+++ b/L_DeusExMachinaII1.lua
@@ -0,0 +1,649 @@
+module("L_DeusExMachinaII1", package.seeall)
+
+local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
+local DEMVERSION = 20400
+
+local SWITCH_TYPE = "urn:schemas-upnp-org:device:BinaryLight:1"
+local SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
+local DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
+local DIMMER_SID = "urn:upnp-org:serviceId:Dimming1"
+
+local STATE_STANDBY = 0
+local STATE_IDLE = 1
+local STATE_CYCLE = 2
+local STATE_SHUTDOWN = 3
+
+local runStamp = 0
+
+local debugMode = true
+
+local function debug(...)
+ if debugMode then
+ local str = "DeusExMachinaII1:" .. arg[1]
+ local ipos = 1
+ while true do
+ local i, j, n
+ i, j, n = string.find(str, "%%(%d+)", ipos)
+ if i == nil then break end
+ n = tonumber(n, 10)
+ if n >= 1 and n < table.getn(arg) then
+ if i == 1 then
+ str = tostring(arg[n+1]) .. string.sub(str, j+1)
+ else
+ str = string.sub(str, 1, i-1) .. tostring(arg[n+1]) .. string.sub(str, j+1)
+ end
+ end
+ ipos = j + 1
+ end
+ luup.log(str)
+ end
+end
+
+local function checkVersion()
+ local ui7Check = luup.variable_get(SID, "UI7Check", lul_device) or ""
+ if ui7Check == "" then
+ luup.variable_set(SID, "UI7Check", "false", lul_device)
+ ui7Check = "false"
+ end
+ if ( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false" ) then
+ luup.variable_set(SID, "UI7Check", "true", lul_device)
+ luup.attr_set("device_json", "D_DeusExMachinaII1_UI7.json", lul_device)
+ luup.reload()
+ end
+end
+
+-- Get numeric variable, or return default value if not set or blank
+local function getVarNumeric( name, dflt )
+ local s = luup.variable_get(SID, name, lul_device)
+ if (s == nil or s == "") then return dflt end
+ s = tonumber(s, 10)
+ if (s == nil) then return dflt end
+ return s
+end
+
+-- Delete a variable (if we can... read on...)
+local function deleteVar( name, devid )
+ if (devid == nil) then devid = luup.device end
+ -- Interestingly, setting a variable to nil with luup.variable_set does nothing interesting; too bad, it
+ -- could have been used to delete variables, since a later get would yield nil anyway. But it turns out
+ -- that using the variableget Luup request with no value WILL delete the variable.
+ local req = "http://127.0.0.1:3480/data_request?id=variableset&DeviceNum=" .. tostring(devid) .. "&serviceId=" .. SID .. "&Variable=" .. name .. "&Value="
+ -- debug("DeusExMachinaII::deleteVar(" .. name .. "): req=" .. tostring(req))
+ local status, result = luup.inet.wget(req)
+ -- debug("DeusExMachinaII::deleteVar(" .. name .. "): status=" .. tostring(status) .. ", result=" .. tostring(result))
+end
+
+-- Shortcut function to return state of SwitchPower1 Status variable
+local function isEnabled()
+ local s = luup.variable_get(SWITCH_SID, "Status", lul_device)
+ if (s == nil or s == "") then return false end
+ return (s ~= "0")
+end
+
+local function isActiveHouseMode()
+ -- Fetch our mask bits that tell us what modes we operate in. If 0, we're not checking house mode.
+ local modebits = getVarNumeric("HouseModes", 0)
+ if (modebits ~= 0) then
+ -- Get the current house mode. There seems to be some disharmony in the correct way to go
+ -- about this, but the method (uncommented) below works.
+ local currentMode = luup.attr_get("Mode", 0) -- alternate method
+
+ -- Check to see if house mode bits are non-zero, and if so, apply current mode as mask.
+ -- If bit is set (current mode is in the bitset), we can run, otherwise skip.
+ local bit = require("bit")
+ -- Get the current house mode (1=Home,2=Away,3=Night,4=Vacation)
+ currentMode = math.pow(2, tonumber(currentMode,10))
+ if (bit.band(modebits, currentMode) == 0) then
+ debug('DeusExMachinaII::isActiveHouseMode(): Current mode bit %1 not set in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
+ return false -- not active in this mode
+ else
+ debug('DeusExMachinaII::isActiveHouseMode(): Current mode bit %1 SET in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
+ end
+ end
+ return true -- default is we're active in the current house mode
+end
+
+-- Get a random delay from two state variables. Error check.
+local function getRandomDelay(minStateName,maxStateName,defMin,defMax)
+ if defMin == nil then defMin = 300 end
+ if defMax == nil then defMax = 1800 end
+ local mind = getVarNumeric(minStateName, defMin)
+ if mind < 1 then mind = 1 elseif mind > 7200 then mind = 7200 end
+ local maxd = getVarNumeric(maxStateName, defMax)
+ if maxd < 1 then maxd = 1 elseif maxd > 7200 then maxd = 7200 end
+ if maxd < mind then maxd = mind end
+ return math.random( mind, maxd )
+end
+
+-- Get sunset time in minutes since midnight. May override for test mode value.
+local function getSunset()
+ -- Figure out our sunset time. Note that if we make this inquiry after sunset, MiOS
+ -- returns the time of tomorrow's sunset. But, that's not different enough from today's
+ -- that it really matters to us, so go with it.
+ local sunset = luup.sunset()
+ local testing = getVarNumeric("TestMode", 0)
+ if (testing ~= 0) then
+ local m = getVarNumeric( "TestSunset", nil ) -- units are minutes since midnight
+ if (m ~= nil) then
+ -- Sub in our test sunset time
+ local t = os.date('*t', sunset)
+ t['hour'] = math.floor(m / 60)
+ t['min'] = math.floor(m % 60)
+ t['sec'] = 0
+ sunset = os.time(t)
+ end
+ debug('getSunset(): testing mode sunset override %1, as timeval is %2', m, sunset)
+ end
+ if (sunset <= os.time()) then sunset = sunset + 86400 end
+ return sunset
+end
+
+local function getSunsetMSM()
+ local t = os.date('*t', getSunset())
+ return t['hour']*60 + t['min']
+end
+
+-- DEM cycles lights between sunset and the user-specified off time. This function returns 0
+-- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
+-- to minutes-since-midnight units.
+local function isBedtime()
+ local testing = getVarNumeric("TestMode", 0)
+ if (testing ~= 0) then luup.log('DeusExMachinaII::isBedtime(): TestMode is on') end
+
+ -- Establish the lights-out time
+ local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
+ local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
+ if (bedtime_tmp ~= nil) then
+ bedtime_tmp = tonumber(bedtime_tmp,10)
+ if (bedtime_tmp >= 0 and bedtime_tmp < 1440) then bedtime = bedtime_tmp end
+ end
+
+ -- Figure out our sunset time.
+ local sunset = getSunsetMSM()
+
+ -- And the current time.
+ local date = os.date('*t')
+ local time = date['hour'] * 60 + date['min']
+
+ -- Figure out if we're betweeen sunset and lightout (ret=0) or not (ret=1)
+ debug('isBedtime(): times (mins since midnight) are now=%1, sunset=%2, bedtime=%3', time, sunset, bedtime)
+ local ret = 1 -- guilty until proven innocent
+ if (bedtime > sunset) then
+ -- Case 1: bedtime is after sunset (i.e. between sunset and midnight)
+ if (time >= sunset and time < bedtime) then
+ ret = 0
+ end
+ else
+ -- Case 2: bedtime is after midnight
+ if (time >= sunset or time < bedtime) then
+ ret = 0
+ end
+ end
+ debug('isBedtime(): returning %1", ret)
+ return ret
+end
+
+-- Take a string and split it around sep, returning table (indexed) of substrings
+-- For example abc,def,ghi becomes t[1]=abc, t[2]=def, t[3]=ghi
+-- Returns: table of values, count of values (integer ge 0)
+local function split(s, sep)
+ local t = {}
+ local n = 0
+ if (#s == 0) then return t,n end -- empty string returns nothing
+ local i,j
+ local k = 1
+ repeat
+ i, j = string.find(s, sep or "%s*,%s*", k)
+ if (i == nil) then
+ table.insert(t, string.sub(s, k, -1))
+ n = n + 1
+ break
+ else
+ table.insert(t, string.sub(s, k, i-1))
+ n = n + 1
+ k = j + 1
+ end
+ until k > string.len(s)
+ return t, n
+end
+
+-- Return true if a specified scene has been run (i.e. on the list)
+local function isSceneOn(spec)
+ local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ for i in string.gfind(stateList, "[^,]+") do
+ if (i == spec) then return true end
+ end
+ return false
+end
+
+-- Mark or unmark a scene as having been run
+local function updateSceneState(spec, isOn)
+ local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ local i
+ local t = {}
+ for i in string.gfind(stateList, "[^,]+") do
+ t[i] = 1
+ end
+ if (isOn) then
+ t[spec] = 1
+ else
+ t[spec] = nil
+ end
+ stateList = ""
+ for i in pairs(t) do stateList = stateList .. "," .. tostring(i) end
+ luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
+end
+
+-- Run "final" scene, if defined. This scene is run after all other targets have been
+-- turned off.
+local function runFinalScene()
+ local scene = getVarNumeric("FinalScene", nil)
+ if (scene ~= nil) then
+ debug("runFinalScene(): running final scene %1", scene)
+ luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=scene }, 0)
+ end
+end
+
+-- Get the list of targets from our device state, parse to table of targets.
+local function getTargetList()
+ local s = luup.variable_get(SID, "Devices", lul_device) or ""
+ return split(s)
+end
+
+-- Remove a target from the target list. Used when the target no longer exists. Linear, poor, but short list and rarely used.
+local function removeTarget(target, tlist)
+ if tlist == nil then tlist = getTargetList() end
+ local i
+ for i = 1,table.getn(tlist) do
+ if tostring(target) == tlist[i] then
+ table.remove(tlist, i)
+ luup.variable_set(SID, "Devices", table.concat(tlist, ","), lul_device)
+ return true
+ end
+ end
+ return false
+end
+
+-- Light on or off? Returns boolean
+local function isDeviceOn(targetid)
+ local first = string.upper(string.sub(targetid, 1, 1))
+ if (first == "S") then
+ debug("isDeviceOn(): handling scene spec %1", targetid)
+ return isSceneOn(targetid)
+ end
+
+ -- Handle as switch or dimmer
+ debug("isDeviceOn(): handling target spec %1", targetid)
+ local r = tonumber(string.match(targetid, '^%d+'), 10)
+ local val = "0"
+ if (luup.devices[r] ~= nil) then
+ if luup.device_supports_service(DIMMER_SID, r) then
+ val = luup.variable_get(DIMMER_SID, "LoadLevelStatus", r)
+ elseif luup.device_supports_service(SWITCH_SID, r) then
+ val = luup.variable_get(SWITCH_SID, "Status", r)
+ end
+ else
+ luup.log("DeusExMachinaII:isDeviceOn(): target spec " .. tostring(targetid) .. ", device " .. tostring(r) .. " not found in luup.devices")
+ removeTarget(targetid)
+ return nil
+ end
+ return val ~= "0"
+end
+
+-- Control target. Target is a string, expected to be a pure integer (in which case the target is assumed to be a switch or dimmer),
+-- or a string in the form Sxx:yy, in which case xx is an "on" scene to run, and yy is an "off" scene to run.
+local function targetControl(targetid, turnOn)
+ debug("targetControl(): targetid=%1, turnOn=%2", targetid, turnOn)
+ local first = string.upper(string.sub(targetid, 1, 1))
+ if first == "S" then
+ debug("targetControl(): handling as scene spec %1", targetid)
+ i, j, onScene, offScene = string.find(string.sub(targetid, 2), "(%d+)-(%d+)")
+ if (i == nil) then
+ luup.log("DeusExMachina:targetControl(): malformed scene spec=" .. tostring(targetid))
+ return
+ end
+ onScene = tonumber(onScene, 10)
+ offScene = tonumber(offScene, 10)
+ if luup.scenes[onScene] == nil or luup.scenes[offScene] == nil then
+ -- Both on scene and off scene must exist (defensive).
+ luup.log("DeusExMachinaII:targetControl(): one or both of the scenes in " .. tostring(targetid) .. " not found in luup.scenes!")
+ return
+ end
+ debug("targetControl(): on scene is %1, off scene is %2", onScene, offScene)
+ local targetScene
+ if turnOn then targetScene = onScene else targetScene = offScene end
+ luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=targetScene }, 0)
+ updateSceneState(targetid, turnOn)
+ else
+ -- Parse the level if this is a dimming target spec
+ local lvl = 100
+ local k = string.find(targetid, '=')
+ if k ~= nil then
+ _, _, targetid, lvl = string.find(targetid, "(%d+)=(%d+)")
+ lvl = tonumber(lvl, 10)
+ end
+ targetid = tonumber(targetid, 10)
+ -- Level for all types is 0 if turning device off
+ if not turnOn then lvl = 0 end
+ if luup.devices[targetid] == nil then
+ -- Device doesn't exist (user deleted, etc.). PHR??? Should remove from Devices state variable.
+ luup.log("DeusExMachinaII:targetControl(): device " .. tostring(targetid) .. " not found in luup.devices")
+ removeTarget(targetid)
+ return
+ end
+ if luup.device_supports_service(DIMMER_SID, targetid) then
+ -- Handle as Dimming1
+ debug("targetControl(): handling %1 as dimmmer, set load level to %2", targetid, lvl)
+ luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
+ elseif luup.device_supports_service(SWITCH_SID, targetid) then
+ -- Handle as SwitchPower1
+ if turnOn then lvl = 1 end
+ debug("targetControl(): handling %1 as switch, setting target to %2", targetid, lvl)
+ luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
+ else
+ luup.log("DeusExMachinaII:targetControl(): don't know how to control target " .. tostring(targetid))
+ end
+ end
+end
+
+-- Get list of targets that are on
+local function getTargetsOn()
+ local devs, max
+ local on = {}
+ local n = 0
+ devs,max = getTargetList()
+ if (max > 0) then
+ local i
+ for i = 1,max do
+ local devOn = isDeviceOn(devs[i])
+ if (devOn ~= nil and devOn) then
+ table.insert(on, devs[i])
+ n = n + 1
+ end
+ end
+ end
+ return on,n
+end
+
+-- Turn off a light, if any is on. Returns 1 if there are more lights to turn off; otherwise 0.
+local function turnOffLight(on)
+ local n
+ if on == nil then
+ on, n = getTargetsOn()
+ else
+ n = table.getn(on)
+ end
+ if (n > 0) then
+ local i = math.random(1, n)
+ local target = on[i]
+ targetControl(target, false)
+ table.remove(on, i)
+ n = n - 1
+ debug(":turnOffLight(): turned %1 OFF, still %2 targets on", target, n)
+ end
+ return (n > 0), on, n
+end
+
+-- Turn off all lights as fast as we can. Transition through SHUTDOWN state during,
+-- in case user has any triggers connected to that state. The caller must immediately
+-- set the next state when this function returns (expected would be STANDBY or IDLE).
+local function clearLights()
+ local devs, count
+ devs, count = getTargetList()
+ luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
+ while count > 0 do
+ targetControl(devs[count], false)
+ count = count - 1
+ end
+ runFinalScene()
+end
+
+-- runOnce() looks to see if a core state variable exists; if not, a one-time initialization
+-- takes place. For us, that means looking to see if an older version of Deus is still
+-- installed, and copying its config into our new config. Then disable the old Deus.
+local function runOnce()
+ local s = luup.variable_get(SID, "Devices", lul_device)
+ if (s == nil) then
+ luup.log("DeusExMachinaII:runOnce(): Devices variable not found, setting up new instance...")
+ -- See if there are variables from older version of DEM
+ -- Start by finding the old Deus device, if there is one...
+ local devList = ""
+ local i, olddev
+ olddev = -1
+ for i,v in pairs(luup.devices) do
+ if (v.device_type == "urn:schemas-futzle-com:device:DeusExMachina:1") then
+ luup.log("DeusExMachinaII:runOnce(): Found old Deus Ex Machina device #" .. tostring(i))
+ olddev = i
+ break
+ end
+ end
+ if (olddev > 0) then
+ -- We found an old Deus device, copy its config into our new state variables
+ local oldsid = "urn:futzle-com:serviceId:DeusExMachina1"
+ s = luup.variable_get(oldsid, "LightsOutTime", olddev)
+ if (s ~= nil) then
+ local n = tonumber(s,10) / 60000
+ luup.variable_set(SID, "LightsOut", n, lul_device)
+ deleteVar("LightsOutTime", lul_device)
+ end
+ s = luup.variable_get(oldsid, "controlCount", olddev)
+ if (s ~= nil) then
+ local n = tonumber(s, 10)
+ local k
+ local t = {}
+ for k = 1,n do
+ s = luup.variable_get(oldsid, "control" .. tostring(k-1), olddev)
+ if (s ~= nil) then
+ table.insert(t, s)
+ end
+ end
+ devList = table.concat(t, ",")
+ deleteVar("controlCount", lul_device)
+ end
+
+ -- Finally, turn off old Deus
+ luup.call_action(oldsid, "SetEnabled", { NewEnabledValue = "0" }, olddev)
+ end
+ luup.variable_set(SID, "Devices", devList, lul_device)
+
+ -- Set up some other default config
+ luup.variable_set(SID, "MinCycleDelay", "300", lul_device)
+ luup.variable_set(SID, "MaxCycleDelay", "1800", lul_device)
+ luup.variable_set(SID, "MinOffDelay", "60", lul_device)
+ luup.variable_set(SID, "MaxOffDelay", "300", lul_device)
+ luup.variable_set(SID, "LightsOut", 1439, lul_device)
+ luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
+ luup.variable_set(SID, "Enabled", "0", lul_device, true)
+ luup.variable_set(SID, "Version", DEMVERSION, lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "0", lul_device, true)
+ luup.variable_set(SWITCH_SID, "Target", "0", lul_device, true)
+ end
+
+ -- Consider per-version changes.
+ s = getVarNumeric("Version", 0)
+ if (s < 20300) then
+ -- v2.3: LightsOutTime (in milliseconds) deprecated, now using LightsOut (in minutes since midnight)
+ luup.log("DeusExMachinaII:runOnce(): updating config, version " .. tostring(s) .. " < 20300")
+ s = luup.variable_get(SID, "LightsOut", lul_device)
+ if (s == nil) then
+ s = getVarNumeric("LightsOutTime") -- get pre-2.3 variable
+ if (s == nil) then
+ luup.variable_set(SID, "LightsOut", 1439, lul_device) -- default 23:59
+ else
+ luup.variable_set(SID, "LightsOut", tonumber(s,10) / 60000, lul_device) -- conv ms to minutes
+ end
+ end
+ deleteVar("LightsOutTime", lul_device)
+ end
+ if (s < 20400) then
+ luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
+ local e = getVarNumeric("Enabled", 0)
+ luup.variable_set(SWITCH_SID, "Status", e, lul_device, true)
+ luup.variable_set(SWITCH_SID, "Target", e, lul_device, true)
+ end
+
+ -- Update version last.
+ luup.variable_set(SID, "Version", DEMVERSION, lul_device)
+end
+
+-- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
+function deusEnable()
+ luup.log("DeusExMachinaII:deusEnable(): enabling...")
+ luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
+ runStamp = os.time()
+ luup.call_delay("deusStep", 1, runStamp, 1)
+ luup.variable_set(SID, "Enabled", "1", lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "1", lul_device)
+end
+
+-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
+-- turn off any controlled lights that are on.
+function deusDisable()
+ local s = getVarNumeric("State", STATE_STANDBY)
+ luup.log("DeusExMachinaII:deusDisable(): disabling...")
+ if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
+ clearLights()
+ end
+ luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
+ luup.variable_set(SID, "Enabled", "0", lul_device)
+ luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
+end
+
+-- Initialize.
+function deusInit(deusDevice)
+ luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-21), initializing...")
+
+ -- One-time stuff
+ runOnce()
+
+ -- Check UI version
+ checkVersion()
+
+ -- Start up if we're enabled
+ if (isEnabled()) then
+ deusEnable()
+ else
+ deusDisable()
+ end
+end
+
+-- Run a cycle. If we're in "bedtime" (i.e. not between our cycle period between sunset and stop),
+-- then we'll shut off any lights we've turned on and queue another run for the next sunset. Otherwise,
+-- we'll toggled one of our controlled lights, and queue (random delay, but soon) for another cycle.
+-- The shutdown of lights also occurs randomly, but can (through device state/config) have different
+-- delays, so the lights going off looks more "natural" (i.e. not all at once just slamming off).
+function deusStep(stepStampCheck)
+ local stepStamp = tonumber(stepStampCheck)
+ luup.log("DeusExMachinaII:deusStep(): wakeup, stamp " .. stepStampCheck)
+ if (stepStamp ~= runStamp) then
+ luup.log("DeusExMachinaII:deusStep(): stamp mismatch, another thread running. Bye!")
+ return
+ end
+ if (not isEnabled()) then
+ luup.log("DeusExMachinaII:deusStep(): not enabled, no more work for this thread...")
+ return
+ end
+
+ -- Get next sunset as seconds since midnight (approx)
+ local sunset = getSunset()
+
+ local currentState = getVarNumeric("State", 0)
+ if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
+ debug("deusStep(): run in state %1, lightsout=%2, sunset=%3, os.time=%4", currentState,
+ luup.variable_get(SID, "LightsOut", lul_device), sunset, os.time())
+ debug("deusStep(): luup variables longitude=%1, latitude=%2, timezone=%3, city=%4, sunset=%5, version=%6",
+ luup.longitude, luup.latitude, luup.timezone, luup.city, luup.sunset(), luup.version)
+ end
+
+ local inActiveTimePeriod = true
+ if (isBedtime() ~= 0) then
+ luup.log("DeusExMachinaII:deusStep(): in lights out time")
+ inActiveTimePeriod = false
+ end
+
+ -- Get going...
+ local nextCycleDelay = 300 -- a default value to keep us out of hot water
+ if (currentState == STATE_STANDBY and not inActiveTimePeriod) then
+ -- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
+ -- Go to IDLE and delay for next sunset.
+ luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...")
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ elseif (not isActiveHouseMode()) then
+ -- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
+ if (currentState ~= STATE_IDLE) then
+ luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE, not in an active house mode.")
+ if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
+ luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ else
+ luup.log("DeusExMachinaII::deusStep(): IDLE in an inactive house mode; waiting for mode change.")
+ end
+
+ -- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
+ -- to re-check house mode, which could change at any time, so we must deal with it.
+ if (inActiveTimePeriod) then
+ nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ else
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ end
+ elseif (not inActiveTimePeriod) then
+ luup.log("DeusExMachinaII::deusStep(): running off cycle")
+ luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
+ if (not turnOffLight()) then
+ -- No more lights to turn off
+ runFinalScene()
+ luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ luup.log("DeusExMachina::deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
+ else
+ nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ end
+ else
+ -- Fully active. Find a random target to control and control it.
+ luup.log("DeusExMachinaII::deusStep(): running toggle cycle")
+ luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
+ nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ local devs, max
+ devs, max = getTargetList()
+ if (max > 0) then
+ local change = math.random(1, max)
+ local devspec = devs[change]
+ if (devspec ~= nil) then
+ local s = isDeviceOn(devspec)
+ if (s ~= nil) then
+ if (s) then
+ -- It's on; turn it off.
+ debug("deusStep(): turn %1 OFF", devspec)
+ targetControl(devspec, false)
+ else
+ -- Turn something on. If we're at the max number of targets we're allowed to turn on,
+ -- turn targets off first.
+ local maxOn = getVarNumeric("MaxTargetsOn")
+ if (maxOn == nil) then maxOn = 0 else maxOn = tonumber(maxOn,10) end
+ if (maxOn > 0) then
+ local on, n
+ on, n = getTargetsOn()
+ while ( n >= maxOn ) do
+ debug("deusStep(): too many targets on, max is %1, have %2, turning one off", maxOn, n)
+ _, on, n = turnOffLight(on)
+ end
+ end
+ debug("deusStep(): turn %1 ON", devspec)
+ targetControl(devspec, true)
+ end
+ end
+ end
+ else
+ luup.log("DeusExMachinaII:deusStep(): no targets to control")
+ end
+ end
+
+ -- Arm for next cycle
+ if nextCycleDelay ~= nil then
+ luup.log("DeusExMachinaII:deusStep(): cycle finished, next in " .. nextCycleDelay .. " seconds")
+ if nextCycleDelay < 1 then nextCycleDelay = 60 end
+ luup.call_delay("deusStep", nextCycleDelay, stepStamp, 1)
+ else
+ luup.log("DeusExMachinaII:deusStep(): nil nextCycleDelay, next cycle not scheduled!")
+ end
+end
From d3e94f3b3aebf382c763c2961610ca18d27f735a Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 23 Dec 2016 19:13:29 -0500
Subject: [PATCH 23/49] Message field to say what we are doing
---
D_DeusExMachinaII1_UI7.json | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index 63784c5..a1081c6 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -103,7 +103,10 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4RC3 2016-12-21 For documentation or to report bugs, please go to the DeusExMachina Github repository This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation.."
+ "text": "DeusExMachina II ver 2.4RC4 2016-12-23
+ For documentation or to report bugs, please go to the DeusExMachina Github repository .
+ This plugin is offered for use as-is and without warranties of any kind. By using this plugin,
+ you agree to assume all risks in connection with its use without limitation."
},
"Display": {
"Top": "80",
@@ -111,6 +114,18 @@
"Width": "200",
"Height": "24"
}
+ }, {
+ "ControlType": "variable",
+ "top": "0",
+ "left": "0",
+ "Display": {
+ "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
+ "Variable": "Message",
+ "Top": "16",
+ "Left": "120",
+ "Width": "200",
+ "Height": "20"
+ }
}]
}, {
"Label": {
From bbb566b74d3746df4fa2a7fe4d4c77968218d5ef Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 23 Dec 2016 19:13:59 -0500
Subject: [PATCH 24/49] Spring cleaning (in winter)
---
README.md | 140 ++++++++++++++++++++++++++++++++++++------------------
1 file changed, 93 insertions(+), 47 deletions(-)
diff --git a/README.md b/README.md
index 13750f2..263acc6 100644
--- a/README.md
+++ b/README.md
@@ -7,21 +7,17 @@ DeusExMachina is a plugin for the MiCasaVerde Vera home automation system. It ta
There are currently two versions of Deus Ex Machina available:
-* Deus Ex Machina -- version 1.1, for UI5; this is the legacy version and although it installs for UI6 and UI7, it does not work properly on those platforms.
+* Deus Ex Machina -- for UI5 (only); this is the legacy version and although it installs for UI6 and UI7, it does not work properly on those platforms. This version is only available from the MiCasaVerde plugin library.
-* Deus Ex Machina II -- version 2.4, for UI7 (only). This version was developed and tested on firmware version 1.7.855, but should work for any full release of UI7 provided by MiCasaVerde.
+* Deus Ex Machina II -- for UI7 (only). This version was developed and tested on firmware version 1.7.855, but should work for any full release of UI7 provided by MiCasaVerde. The current release is available via both GitHub and the MiCasaVerde plugin library. Advanced builds are also available in the GitHub repository.
### History ###
-DeusExMachina was originally written and published in 2012 by Andy Lintner (beowulfe), and maintained by Andy through the 1.x versions. In May 2016, Andy turned the project over to Patrick Rigney (toggledbits here on Github, rigpapa in the MCV/Mios world) for ongoing support (version 2.0 onward).
-
-For information about modifications, fixes and enhancements, please see the Changelog.
+DeusExMachina was originally written and published in 2012 by Andy Lintner (beowulfe), and maintained by Andy through the 1.x versions. In May 2016, Andy turned the project over to Patrick Rigney (toggledbits here on Github, rigpapa in the MCV/MiOS world) for ongoing support (version 2.0 onward). At this point, the plugin became known as Deus Ex Machina II, or just DEMII.
### How It Works ###
-When DeusExMachina is activated, it first waits until sunset. It then begins to toggle lights on the list of controlled devices (which is user-configured), with a random delay of between 5 and 30 minutes between each device. Then, when a user-configured "lights out" time is reached, the plug-in begins to turn randomly turn off any of the controlled lights that are on, until all are off. It then waits until the next day to resume its work at sunset, if it is still enabled.
-
-In the "lights out" mode, DeusExMachina prior to version 2.0 used the same 5-30 minute range of delays to turn off each light. If a large number of lights was configured, it could potentially take hours past the user-specified "lights out" time to turn them all off. As of version 2.0, the default range is between 1 and 5 minutes. This can be changed as further described below.
+When Deus Ex Machina II (DEMII) is enabled, it first waits until two conditions are satisfied: the current time is at or after sunset, and the "house mode" is one of the selected modes in which DEMII is configured by the user to be active. If both conditions are met, DEMII enters a cycle of turning a set of user-selected lights on and off at random intervals. This continues until the user-configured "lights out" time, at which point DEMII begins its shutdown cycle, in which any of the user-selected lights that are on are turned off at random intervals until all lights are off. DEMII then waits until the next day's sunset.
For more information, see Additional Documentation below.
@@ -37,50 +33,66 @@ DeusExMachina is offered under GPL (the GNU Public License).
#### Installation ####
-The plugin is installed in the usual way:
-
-* On UI7, go to Apps in the left navigation, and click on "Install Apps". Search for "Deus Ex Machina II" (make sure you include the "II" at the end to get the UI7-compatible version), and then click the
-"Details" button in its listing of the search results. From here, simply click "Install" and wait for the install to complete. A full refresh of the UI is necessary (e.g. Ctrl-F5 on Windows) after installation.
-
-* On UI5, click on APPS in the top navigation bar, then click "Install Apps". Search for "Deus Ex Machina". The search results will show "Deus Ex Machina" and "Deus Ex Machina II". At this time, it is recommended
-that you install the legacy version only on UI5 (unless you want to help me test the new version), which is "Deus Ex Machina". Click on the "Details" button, and then click "Install". Once the install completes,
-one or more full refreshes of the browser (e.g. Ctrl-F5 on Windows) will be necessary.
+The plugin is installed in the usual way: go to Apps in the left navigation, and click on "Install Apps".
+Search for "Deus Ex Machina II" (make sure you include the "II" at the end to get the UI7-compatible version),
+and then click the "Details" button in its listing of the search results. From here, simply click "Install"
+and wait for the install to complete. A full refresh of the UI is necessary (e.g. Ctrl-F5 on Windows) after installation.
Once you have installed the plugin and refreshed the browser, you can proceed to device configuation.
#### Simple Configuration ####
-Deus Ex Machina's "Configure" tab allows you to set up the set of lights that should be controlled, and the time at which DEM should begin shutting lights off to simulate the house occupants going to sleep.
+Deus Ex Machina's "Configure" tab gives you a set of simple controls to control the behavior of your vacation haunt.
+
+##### Lights-Out Time #####
+
+The "Lights Out" time is a time, expressed in 24-hour HH:MM format, that is the time at which lights should begin
+shutting off. This time should be after sunset. Keep in mind that sunset is a moving target, and
+at certain times of year in some places can be quite late, so a Lights Out time of 20:15, for example, may not be
+a good choice for the longest days of summer. The lights out time can be a time after midnight.
+
+##### House Modes #####
-The "Lights Out" time is a time, expressed in 24-hour HH:MM format, that is the time at which lights should begin shutting off. This time should be after sunset. Keep in mind that sunset is a moving target, and
-at certain times of year in some places can be quite late, so a Lights Out time of 20:15, for example, may be too early. The lights out time can be a time after midnight.
+The next group of controls is the House Modes in which DEMII should be active when enabled. If no house mode is selected,
+DEMII will operate in _any_ house mode.
-UI7 introduced the concept of "House Modes." Version 2.3 and beyond of Deus Ex Machina have the ability to run only when the
-house is in one or more selected house modes. A set of checkboxes is used to selected which modes allow Deus Ex Machina to run.
-If no modes are chosen, it is the same as choosing all modes (Deus operates in any house mode).
+##### Controlled Devices #####
+Next is a set of checkboxes for each of the devices you'd like DEMII to control.
Selecting the devices to be controlled is a simple matter of clicking the check boxes. Because the operating cycle of
the plug-in is random, any controlled device may be turned on and off several times during the cycling period (between sunset and Lights Out time).
-As of version 2.4, lights on dimmers can be set to any level by setting the slider that appears to the right of the device name. Non-dimming devices are simply turned on and off.
+Dimming devices can be set to any level by setting the slider that appears to the right of the device name.
+Non-dimming devices are simply turned on and off (no dimmer slider is shown for these devices).
-As of version 2.4, all devices are listed that implement the SwitchPower1 and Dimming1 services. This leads to some oddities,
-like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example, might do when you try to turn it off and on like a light, so be careful selecting these devices.
+> Note: all devices are listed that implement the SwitchPower1 and Dimming1 services. This leads to some oddities,
+> like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example,
+> might do when you try to turn it off and on like a light, so be careful selecting these devices.
-Also new for version 2.4 is the ability to run scenes during the random cycling period. Scenes must be specified in pairs, with
+##### Scene Control #####
+
+The next group of settings allows you to use scenes with DEMII.
+Scenes must be specified in pairs, with
one being the "on" scene and the other being an "off" scene. This not only allows more patterned use of lights, but also gives the user
-the ability to handle device-specific capabilities that would be difficult to implement in DEMII. For example, while DEMII can now
+the ability to handle device-specific capabilities that would be difficult to implement in DEMII. For example, while DEMII can
turn Philips Hue lights on and off (to dimming levels, even), it cannot control their color because there's no UI for that in
-DEMII. But a scene could be used to control that light or a group of lights, with their color.
+DEMII. But a scene could be used to control that light or a group of lights, with their color, as an alternative to direct control by DEMII.
+
+Both scenes and individual devices (from the device list above) can be used simultaneously.
-Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously. If this limit is 0, there is no limit enforced.
+##### Maximum "On" Targets #####
-Finally, 2.4 adds the ability for a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
+Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
+If this limit is 0, there is no limit enforced. If you have DEMII control a large number of devices, it's probably not a bad idea to
+set this value to some reasonable limit.
-#### Control by Scene ####
+##### Final Scene #####
-As of version 2.0 and on UI7, DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
+DEMII allows a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
-A Lua interface is also supported since version 1.1 for both UI5 and UI7. All versions of support the SetEnabled action, although for DEMII versions 2.4 and higher, the use of SetTarget in the standard SwitchPower1 service is preferred. Examples (the "0|1" means use either 0 or 1 to disable or enable, respectively):
+#### Control of DEMII by Scenes ####
+
+DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
+A Lua interface is also supported. All versions of support the SetEnabled action, although for DEMII versions 2.4 and higher, the use of SetTarget in the standard SwitchPower1 service is preferred. Examples (the "0|1" means use either 0 or 1 to disable or enable, respectively):
```
-- Preferred for DEMII versions 2.4 and higher:
@@ -93,18 +105,9 @@ luup.call_action("urn:toggledbits-com:serviceId:DeusExMachinaII1", "SetEnabled",
luup.call_action("urn:futzle-com:serviceId:DeusExMachina1", "SetEnabled", { NewEnabledValue = "0|1" }, deviceID)
```
-Note that when disabling Deus Ex Machina from a scene or the user interface, versions 1.1 and 2.x (DEMII) operate differently. Version 1.1 will simply stop cycling lights, leaving on any controlled lights it may have turned on.
-DEMII, however, will turn off all controlled lights _if it was in the cycling period (between sunset and lights out time) at the time it was disabled_.
-
-DEMII version 2.0 also added the ability for a change of DeusExMachina's operating state to be used as trigger in scenes and other places where events can be watched (e.g. Program Logic plugins, etc.). This also works on UI7 only.
-
#### Triggers ####
-Version 2.0 on UI7 supports events (e.g. to trigger a scene or use with Program Logic Event Generator) for its state changes.
-
-If the "device is enabled or disabled" event is chosen, the trigger will fire when DEMII's state is changed to enabled or disabled (you will be given the option to choose which).
-
-For the "operating mode changes" event, the trigger fires when DEMII's operating mode changes. DEMII's operating modes are:
+DEMII signals changes to its enabled/disabled state, and changes to its internal operating mode. These can be used as triggers for scenes or notifications. DEMII's operating modes are:
* Standby - DEMII is disabled (this is equivalent to the "device is disabled" state event);
@@ -123,7 +126,50 @@ respectively. DEMII will also transition into or out of Standby mode immediately
#### Cycle Timing ####
-Version 2.0 has added device state variables to alter the default cycle timing. They can be changed by accessing them through the "Advanced" tab in the device user interface.
-The random delay between turning lights on or off is between 5 and 30 minutes by default. By setting `MinCycleTime` and `MaxCycleTime` (integer number of seconds,
-default 300 and 1800, respectively), the user can modify the default settings for on/off cycling. Similarly the `MinOffTime` and `MaxOffTime` variables (default 60 and 300 seconds,
-respectively) change the rate of the "lights out" mode (i.e. the transition to all lights out at the user-configured time).
+DEMII's cycle timing is controlled by a set of state variables. By default, DEMII's random cycling of lights occurs at randomly selected intervals between 300 seconds (5 minutes) and 1800 seconds (30 minutes), as determined by the `MinCycleDelay` and `MaxCycleDelay` variables. You may change these values to customize the cycling time for your application.
+
+When DEMII is in its "lights out" (shut-off) mode, it uses a different set of shorter (by default) cycle times, to more closely imitate actual human behavior. The random interval for lights-out is between 60 seconds and 300 seconds (5 minutes), as determined by `MinOffDelay` and `MaxOffDelay`. These intervals could be kept short, particularly if DEMII is controlling a large number of lights.
+
+#### Troubleshooting ####
+
+If DEMII isn't behaving as expected, post a message in the MCV forums
+[in this thread](http://forum.micasaverde.com/index.php/topic,11333.0.html)
+or open up an issue in the
+[GitHub repository](https://github.com/toggledbits/DeusExMachina/issues).
+
+Please don't just say "DEMII isn't working for me." I can't tell you how long a piece of string is without seeing the piece of string. Give me details of what you are doing, how you are configured, and what behavior you observe.
+
+##### Test Mode and Log Output #####
+
+If I'm troubleshooting a problem with you, I may ask you to enable test mode, run DEMII a bit, and send me the log output. Here's how you do that:
+
+1. Go into the setting for the DEMII device, and click the "Advanced" tab.
+1. Click on the Variables tab.
+1. Set the TestMode variable to 1 (just change the field and hit the TAB key)
+1. If requested, set the TestSunset value to whatever I ask you (this allows the sunset time to be overriden so we don't have to wait for real sunset to see what DEMII is doing).
+
+### FAQ ###
+
+
+ What happens if DEMII is enabled afer sunset? Does it wait until the next day to start running?
+ No. If DEMII is enabled during its active period (between sunset and the configured "lights out" time,
+ it will begin cycling the configured devices and scenes.
+
+ What's the difference between House Mode and Enabled/Disabled? Can I just use House Mode to enable and disable DEMII?
+ The enabled/disabled state of DEMII is the "big red button" for its operation. If you configure DEMII to only run in certain
+ house modes, then you can theoretically leave DEMII enabled all the time, as it will operate (cycle lights) only when a
+ selected house mode is active. But, some people don't use the House Modes for various reasons, so having a master switch
+ for DEMII is necessary.
+
+ I have a feature request. Will you implement it?
+ Absolutely definitely maybe. I'm willing to listen to what you want to do. But, keep in mind, nobody's getting rich writing Vera
+ plugins, and I do other things that put food on my table. And, what seems like a good idea to you may be just that: a good idea for
+ the way _you_ want to use it. The more generally applicable your request is, the higher the likelihood that I'll entertain it. What
+ I don't want to do is over-complicate this plug-in so it begins to rival PLEG for size and weight (no disrespect intended there at
+ all--I'm a huge PLEG fan and use it extensively, but, dang). DEMII really has a simple job: make lights go on and off to cast a serious
+ shadow of doubt in the mind of some knucklehead who might be thinking your house is empty and ripe for his picking. In any case,
+ the best way to give me feature requests is to open up an issue (if you have a list, one issue per feature, please) in the
+ [GitHub repository](https://github.com/toggledbits/DeusExMachina/issues). Second best is sending me a message via the MCV forums
+ (I'm user `rigpapa`).
+
+
From 0500b3d57b7b4ebc27782b90e374c32e03d668f1 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 23 Dec 2016 19:14:16 -0500
Subject: [PATCH 25/49] Message field to say what we are doing
---
L_DeusExMachinaII1.lua | 65 ++++++++++++++++++++++++++++++------------
1 file changed, 46 insertions(+), 19 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index 9048e64..3e54f97 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -15,7 +15,7 @@ local STATE_SHUTDOWN = 3
local runStamp = 0
-local debugMode = true
+local debugMode = false
local function debug(...)
if debugMode then
@@ -68,9 +68,23 @@ local function deleteVar( name, devid )
-- could have been used to delete variables, since a later get would yield nil anyway. But it turns out
-- that using the variableget Luup request with no value WILL delete the variable.
local req = "http://127.0.0.1:3480/data_request?id=variableset&DeviceNum=" .. tostring(devid) .. "&serviceId=" .. SID .. "&Variable=" .. name .. "&Value="
- -- debug("DeusExMachinaII::deleteVar(" .. name .. "): req=" .. tostring(req))
+ debug("deleteVar(%1,%2) wget %3", name, devid, req)
local status, result = luup.inet.wget(req)
- -- debug("DeusExMachinaII::deleteVar(" .. name .. "): status=" .. tostring(status) .. ", result=" .. tostring(result))
+ debug("deleteVar(%1,%2) status=%3, result=%4", name, devid, status, result)
+end
+
+local function pad(n)
+ if (n < 10) then return "0" .. n end
+ return n;
+end
+
+local function timeToString(t)
+ if t == nil then t = os.time() end
+ return pad(t['hour']) .. ':' .. pad(t['min']) .. ':' .. pad(t['sec'])
+end
+
+local function setMessage(s)
+ luup.variable_set(SID, "Message", s or "", lul_device)
end
-- Shortcut function to return state of SwitchPower1 Status variable
@@ -94,10 +108,10 @@ local function isActiveHouseMode()
-- Get the current house mode (1=Home,2=Away,3=Night,4=Vacation)
currentMode = math.pow(2, tonumber(currentMode,10))
if (bit.band(modebits, currentMode) == 0) then
- debug('DeusExMachinaII::isActiveHouseMode(): Current mode bit %1 not set in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
+ debug('DeusExMachinaII:isActiveHouseMode(): Current mode bit %1 not set in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
return false -- not active in this mode
else
- debug('DeusExMachinaII::isActiveHouseMode(): Current mode bit %1 SET in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
+ debug('DeusExMachinaII:isActiveHouseMode(): Current mode bit %1 SET in %2', string.format("0x%x", currentMode), string.format("0x%x", modebits))
end
end
return true -- default is we're active in the current house mode
@@ -111,7 +125,7 @@ local function getRandomDelay(minStateName,maxStateName,defMin,defMax)
if mind < 1 then mind = 1 elseif mind > 7200 then mind = 7200 end
local maxd = getVarNumeric(maxStateName, defMax)
if maxd < 1 then maxd = 1 elseif maxd > 7200 then maxd = 7200 end
- if maxd < mind then maxd = mind end
+ if maxd <= mind then return mind end
return math.random( mind, maxd )
end
@@ -131,8 +145,8 @@ local function getSunset()
t['min'] = math.floor(m % 60)
t['sec'] = 0
sunset = os.time(t)
+ debug('getSunset(): testing mode sunset override %1, as timeval is %2', m, sunset)
end
- debug('getSunset(): testing mode sunset override %1, as timeval is %2', m, sunset)
end
if (sunset <= os.time()) then sunset = sunset + 86400 end
return sunset
@@ -148,7 +162,10 @@ end
-- to minutes-since-midnight units.
local function isBedtime()
local testing = getVarNumeric("TestMode", 0)
- if (testing ~= 0) then luup.log('DeusExMachinaII::isBedtime(): TestMode is on') end
+ if (testing ~= 0) then
+ luup.log('DeusExMachinaII:isBedtime(): TestMode is on')
+ debugMode = true
+ end
-- Establish the lights-out time
local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
@@ -179,7 +196,7 @@ local function isBedtime()
ret = 0
end
end
- debug('isBedtime(): returning %1", ret)
+ debug("isBedtime(): returning %1", ret)
return ret
end
@@ -379,7 +396,7 @@ local function turnOffLight(on)
targetControl(target, false)
table.remove(on, i)
n = n - 1
- debug(":turnOffLight(): turned %1 OFF, still %2 targets on", target, n)
+ debug("turnOffLight(): turned %1 OFF, still %2 targets on", target, n)
end
return (n > 0), on, n
end
@@ -488,6 +505,7 @@ end
-- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
function deusEnable()
+ setMessage("Enabling...")
luup.log("DeusExMachinaII:deusEnable(): enabling...")
luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
runStamp = os.time()
@@ -499,6 +517,7 @@ end
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
-- turn off any controlled lights that are on.
function deusDisable()
+ setMessage("Disabling...")
local s = getVarNumeric("State", STATE_STANDBY)
luup.log("DeusExMachinaII:deusDisable(): disabling...")
if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
@@ -507,11 +526,13 @@ function deusDisable()
luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
luup.variable_set(SID, "Enabled", "0", lul_device)
luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
+ setMessage("")
end
-- Initialize.
function deusInit(deusDevice)
- luup.log("DeusExMachinaII::deusInit(): Version 2.4RC3 (2016-12-21), initializing...")
+ setMessage("Initializing...")
+ luup.log("DeusExMachinaII:deusInit(): Version 2.4RC4 (2016-12-23), initializing...")
-- One-time stuff
runOnce()
@@ -549,7 +570,7 @@ function deusStep(stepStampCheck)
local currentState = getVarNumeric("State", 0)
if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
- debug("deusStep(): run in state %1, lightsout=%2, sunset=%3, os.time=%4", currentState,
+ debug("deusStep(): step in state %1, lightsout=%2, sunset=%3, os.time=%4", currentState,
luup.variable_get(SID, "LightsOut", lul_device), sunset, os.time())
debug("deusStep(): luup variables longitude=%1, latitude=%2, timezone=%3, city=%4, sunset=%5, version=%6",
luup.longitude, luup.latitude, luup.timezone, luup.city, luup.sunset(), luup.version)
@@ -557,7 +578,7 @@ function deusStep(stepStampCheck)
local inActiveTimePeriod = true
if (isBedtime() ~= 0) then
- luup.log("DeusExMachinaII:deusStep(): in lights out time")
+ debug("deusStep(): in lights out time")
inActiveTimePeriod = false
end
@@ -566,17 +587,18 @@ function deusStep(stepStampCheck)
if (currentState == STATE_STANDBY and not inActiveTimePeriod) then
-- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
-- Go to IDLE and delay for next sunset.
- luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...")
+ luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...")
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
elseif (not isActiveHouseMode()) then
-- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
if (currentState ~= STATE_IDLE) then
- luup.log("DeusExMachinaII::deusStep(): transitioning to IDLE, not in an active house mode.")
+ luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE, not in an active house mode.")
if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
else
- luup.log("DeusExMachinaII::deusStep(): IDLE in an inactive house mode; waiting for mode change.")
+ luup.log("DeusExMachinaII:deusStep(): IDLE in an inactive house mode; waiting for mode change.")
end
-- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
@@ -586,21 +608,24 @@ function deusStep(stepStampCheck)
else
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
end
+ setMessage("Waiting for active house mode")
elseif (not inActiveTimePeriod) then
- luup.log("DeusExMachinaII::deusStep(): running off cycle")
+ luup.log("DeusExMachinaII:deusStep(): running off cycle")
luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
if (not turnOffLight()) then
-- No more lights to turn off
runFinalScene()
luup.variable_set(SID, "State", STATE_IDLE, lul_device)
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- luup.log("DeusExMachina::deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
+ luup.log("DeusExMachina:deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
+ setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
else
nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
+ setMessage("Shut-off cycle, next " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
end
else
-- Fully active. Find a random target to control and control it.
- luup.log("DeusExMachinaII::deusStep(): running toggle cycle")
+ luup.log("DeusExMachinaII:deusStep(): running toggle cycle")
luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
local devs, max
@@ -633,7 +658,9 @@ function deusStep(stepStampCheck)
end
end
end
+ setMessage("Cycling; next " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
else
+ setMessage("Nothing to do")
luup.log("DeusExMachinaII:deusStep(): no targets to control")
end
end
From 2a304b113f22a4c064b54298a7a733506923c84f Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 27 Dec 2016 09:40:00 -0500
Subject: [PATCH 26/49] More FAQ and troubleshooting commentary
---
README.md | 99 ++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 65 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index 263acc6..e81ce18 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-DeusExMachina: The Vacation Plugin
+DeusExMachinaII: The Vacation Plugin
=============
-### Introduction ###
+## Introduction ##
DeusExMachina is a plugin for the MiCasaVerde Vera home automation system. It takes over your house while you're away on vacation by creating a ghost that moves from room to room, turning on and off lights. Simply specify the lights you want to have controlled by the plugin, specify a "Lights Out" time when lights will begin to turn off, and come sundown DeusExMachina will take over.
@@ -11,27 +11,27 @@ There are currently two versions of Deus Ex Machina available:
* Deus Ex Machina II -- for UI7 (only). This version was developed and tested on firmware version 1.7.855, but should work for any full release of UI7 provided by MiCasaVerde. The current release is available via both GitHub and the MiCasaVerde plugin library. Advanced builds are also available in the GitHub repository.
-### History ###
+## History ##
DeusExMachina was originally written and published in 2012 by Andy Lintner (beowulfe), and maintained by Andy through the 1.x versions. In May 2016, Andy turned the project over to Patrick Rigney (toggledbits here on Github, rigpapa in the MCV/MiOS world) for ongoing support (version 2.0 onward). At this point, the plugin became known as Deus Ex Machina II, or just DEMII.
-### How It Works ###
+## How It Works ##
When Deus Ex Machina II (DEMII) is enabled, it first waits until two conditions are satisfied: the current time is at or after sunset, and the "house mode" is one of the selected modes in which DEMII is configured by the user to be active. If both conditions are met, DEMII enters a cycle of turning a set of user-selected lights on and off at random intervals. This continues until the user-configured "lights out" time, at which point DEMII begins its shutdown cycle, in which any of the user-selected lights that are on are turned off at random intervals until all lights are off. DEMII then waits until the next day's sunset.
For more information, see Additional Documentation below.
-### Reporting Bugs/Enhancement Requests ###
+## Reporting Bugs/Enhancement Requests ##
Bug reports and enhancement requests are welcome! Please use the "Issues" link for the repository to open a new bug report or make an enhancement request.
-### License ###
+## License ##
DeusExMachina is offered under GPL (the GNU Public License).
-### Additional Documentation ###
+## Additional Documentation ##
-#### Installation ####
+### Installation ###
The plugin is installed in the usual way: go to Apps in the left navigation, and click on "Install Apps".
Search for "Deus Ex Machina II" (make sure you include the "II" at the end to get the UI7-compatible version),
@@ -40,23 +40,23 @@ and wait for the install to complete. A full refresh of the UI is necessary (e.g
Once you have installed the plugin and refreshed the browser, you can proceed to device configuation.
-#### Simple Configuration ####
+### Simple Configuration ###
Deus Ex Machina's "Configure" tab gives you a set of simple controls to control the behavior of your vacation haunt.
-##### Lights-Out Time #####
+#### Lights-Out Time ####
The "Lights Out" time is a time, expressed in 24-hour HH:MM format, that is the time at which lights should begin
shutting off. This time should be after sunset. Keep in mind that sunset is a moving target, and
at certain times of year in some places can be quite late, so a Lights Out time of 20:15, for example, may not be
a good choice for the longest days of summer. The lights out time can be a time after midnight.
-##### House Modes #####
+#### House Modes ####
The next group of controls is the House Modes in which DEMII should be active when enabled. If no house mode is selected,
DEMII will operate in _any_ house mode.
-##### Controlled Devices #####
+#### Controlled Devices ####
Next is a set of checkboxes for each of the devices you'd like DEMII to control.
Selecting the devices to be controlled is a simple matter of clicking the check boxes. Because the operating cycle of
@@ -68,7 +68,7 @@ Non-dimming devices are simply turned on and off (no dimmer slider is shown for
> like some motion sensors and thermostats being listed. It may not be entirely obvious (or standard) what a thermostat, for example,
> might do when you try to turn it off and on like a light, so be careful selecting these devices.
-##### Scene Control #####
+#### Scene Control ####
The next group of settings allows you to use scenes with DEMII.
Scenes must be specified in pairs, with
@@ -79,35 +79,32 @@ DEMII. But a scene could be used to control that light or a group of lights, wit
Both scenes and individual devices (from the device list above) can be used simultaneously.
-##### Maximum "On" Targets #####
+#### Maximum "On" Targets ####
Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
If this limit is 0, there is no limit enforced. If you have DEMII control a large number of devices, it's probably not a bad idea to
set this value to some reasonable limit.
-##### Final Scene #####
+#### Final Scene ####
DEMII allows a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
-#### Control of DEMII by Scenes ####
+### Control of DEMII by Scenes and Lua ###
-DeusExMachina can be enabled or disabled like a light switch in scenes, through the regular graphical interface (no Lua required).
-A Lua interface is also supported. All versions of support the SetEnabled action, although for DEMII versions 2.4 and higher, the use of SetTarget in the standard SwitchPower1 service is preferred. Examples (the "0|1" means use either 0 or 1 to disable or enable, respectively):
+DeusExMachina can be enabled or disabled like a light switch in scenes or through the regular graphical interface (no Lua required),
+or by scripting in Lua.
+DEMII implements the SwitchPower1 service, so enabling and disabling is the same as turning a light switch on and off:
+you simply use the SetTarget action to enable (newTargetValue=1) or disable (newTargetValue=0) DEMII.
+The MiOS GUI for devices and scenes takes care of this for you in its code; if scripting in Lua, you simply do this:
```
--- Preferred for DEMII versions 2.4 and higher:
-luup.call_action("urn:upnp-org-serviceId:SwitchPower1", "SetTarget", { newTargetValue = "0|1" }, deviceID)
-
--- Also works for all versions of DEMII:
-luup.call_action("urn:toggledbits-com:serviceId:DeusExMachinaII1", "SetEnabled", { NewEnabledValue = "0|1" }, deviceID)
-
--- For the old Deus Ex Machina plugin (v1.1 and earlier) running on UI5 or UI7, do this:
-luup.call_action("urn:futzle-com:serviceId:DeusExMachina1", "SetEnabled", { NewEnabledValue = "0|1" }, deviceID)
+luup.call_action("urn:upnp-org-serviceId:SwitchPower1", "SetTarget", { newTargetValue = "0|1" }, pluginDeviceId)
```
-#### Triggers ####
+### Triggers ###
-DEMII signals changes to its enabled/disabled state, and changes to its internal operating mode. These can be used as triggers for scenes or notifications. DEMII's operating modes are:
+DEMII signals changes to its enabled/disabled state and changes to its internal operating mode.
+These can be used as triggers for scenes or notifications. DEMII's operating modes are:
* Standby - DEMII is disabled (this is equivalent to the "device is disabled" state event);
@@ -124,22 +121,28 @@ is disabled (at which point it goes to Standby).
It should be noted that DEMII can enter Cycling or Shut-off mode immediately, without passing through Ready, if it is enabled after sunset or after the "lights out" time,
respectively. DEMII will also transition into or out of Standby mode immediately and from any other mode when disabled or enabled, respectively.
-#### Cycle Timing ####
+### Cycle Timing ###
DEMII's cycle timing is controlled by a set of state variables. By default, DEMII's random cycling of lights occurs at randomly selected intervals between 300 seconds (5 minutes) and 1800 seconds (30 minutes), as determined by the `MinCycleDelay` and `MaxCycleDelay` variables. You may change these values to customize the cycling time for your application.
When DEMII is in its "lights out" (shut-off) mode, it uses a different set of shorter (by default) cycle times, to more closely imitate actual human behavior. The random interval for lights-out is between 60 seconds and 300 seconds (5 minutes), as determined by `MinOffDelay` and `MaxOffDelay`. These intervals could be kept short, particularly if DEMII is controlling a large number of lights.
-#### Troubleshooting ####
+### Troubleshooting ###
+
+If you're not sure what DEMII is going, the easiest way to see is to go into the Settings interface for the plugin.
+There is a text field to the right of the on/off switch in that interface that will tell you what DEMII is currently
+doing (when enabled; it's blank when DEMII is disabled).
If DEMII isn't behaving as expected, post a message in the MCV forums
[in this thread](http://forum.micasaverde.com/index.php/topic,11333.0.html)
or open up an issue in the
[GitHub repository](https://github.com/toggledbits/DeusExMachina/issues).
-Please don't just say "DEMII isn't working for me." I can't tell you how long a piece of string is without seeing the piece of string. Give me details of what you are doing, how you are configured, and what behavior you observe.
+Please don't just say "DEMII isn't working for me." I can't tell you how long your piece of string is without seeing
+your piece of string. Give me details of what you are doing, how you are configured, and what behavior you observe.
+Screen shots help. In many cases, log output may be needed.
-##### Test Mode and Log Output #####
+#### Test Mode and Log Output ####
If I'm troubleshooting a problem with you, I may ask you to enable test mode, run DEMII a bit, and send me the log output. Here's how you do that:
@@ -147,13 +150,41 @@ If I'm troubleshooting a problem with you, I may ask you to enable test mode, ru
1. Click on the Variables tab.
1. Set the TestMode variable to 1 (just change the field and hit the TAB key)
1. If requested, set the TestSunset value to whatever I ask you (this allows the sunset time to be overriden so we don't have to wait for real sunset to see what DEMII is doing).
+1. After operating for a while, I'll ask you to email me the log file (`/etc/cmh/LuaPNP.log` on your Vera). This will require you
+to log in to your Vera directly with ssh, or use the Vera's native "write log to USB drive" function, or use one of the many
+log capture scripts that's available.
-### FAQ ###
+Above all, I ask that you please be patient. You probably already know that it an be challenging at times to figure out
+what's going on in your Vera's head. It's no different for developers.
+
+## FAQ ##
+ My lights aren't cycling at sunset. Why?
+ The most common reasons that lights don't start cycling at midnight are:
+ The time and location on your Vera are not set correctly. Go into Settings > Location on your
+ Vera and make sure everything is correct for the Vera's physical location. Remember that in
+ the western hemisphere (North, Central & South America, principally) your longitude will
+ be a negative number. If you are below the equator, latitude will be negative. If you're not
+ sure what your latitude/longitude are, use a site like [MyGeoPosition.com](http://mygeoposition.com).
+ If you make any changes to your time or location configuration, restart your Vera.
+ You're not waiting long enough. DEMII doesn't instantly jump into action at sunset, it employs its
+ configured cycle delays as well, so cycling will usually begin sometime after sunset, up to the
+ configured maximum cycle delay (30 minutes by default).
+ Your house mode isn't "active." If you've configured DEMII to operate only in certain house modes,
+ make sure you're in one of those modes, otherwise DEMII will sit, even though it's enabled.
+
+
+
+ I made configuration changes, but when I go back into configuration, they seem to be back to the old
+ settings.
+ Refresh your browser or flush your browser cache. On most browsers, you do this by using the F5 key, or
+ Ctrl-F5, or Command + R or Option + R on Macs.
+
What happens if DEMII is enabled afer sunset? Does it wait until the next day to start running?
No. If DEMII is enabled during its active period (between sunset and the configured "lights out" time,
- it will begin cycling the configured devices and scenes.
+ it will begin cycling the configured devices and scenes. If you enable DEMII after "lights-out," it will
+ wait until the next sunset.
What's the difference between House Mode and Enabled/Disabled? Can I just use House Mode to enable and disable DEMII?
The enabled/disabled state of DEMII is the "big red button" for its operation. If you configure DEMII to only run in certain
From 0e5589de765b5f1cf58dc2a4d8dc7c78a1b38ba6 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 27 Dec 2016 09:48:35 -0500
Subject: [PATCH 27/49] Smooth out language
---
README.md | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index e81ce18..ffc6e77 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@ When DEMII is in its "lights out" (shut-off) mode, it uses a different set of sh
If you're not sure what DEMII is going, the easiest way to see is to go into the Settings interface for the plugin.
There is a text field to the right of the on/off switch in that interface that will tell you what DEMII is currently
-doing (when enabled; it's blank when DEMII is disabled).
+doing when enabled (it's blank when DEMII is disabled).
If DEMII isn't behaving as expected, post a message in the MCV forums
[in this thread](http://forum.micasaverde.com/index.php/topic,11333.0.html)
@@ -139,23 +139,25 @@ or open up an issue in the
[GitHub repository](https://github.com/toggledbits/DeusExMachina/issues).
Please don't just say "DEMII isn't working for me." I can't tell you how long your piece of string is without seeing
-your piece of string. Give me details of what you are doing, how you are configured, and what behavior you observe.
+_your_ piece of string. Give me details of what you are doing, how you are configured, and what behavior you observe.
Screen shots help. In many cases, log output may be needed.
#### Test Mode and Log Output ####
If I'm troubleshooting a problem with you, I may ask you to enable test mode, run DEMII a bit, and send me the log output. Here's how you do that:
-1. Go into the setting for the DEMII device, and click the "Advanced" tab.
-1. Click on the Variables tab.
-1. Set the TestMode variable to 1 (just change the field and hit the TAB key)
+1. Go into the settings for the DEMII device, and click the "Advanced" tab.
+1. Click on the "Variables" tab.
+1. Set the "TestMode" variable to 1 (just change the field and hit the TAB key). If the variable doesn't exist, you'll need to create it using the "New Service" tab, which requires you to enter the service ID _exactly_ as shown here (use copy/paste if possible): `urn:toggledbits-com:serviceId:DeusExMachinaII1`
1. If requested, set the TestSunset value to whatever I ask you (this allows the sunset time to be overriden so we don't have to wait for real sunset to see what DEMII is doing).
-1. After operating for a while, I'll ask you to email me the log file (`/etc/cmh/LuaPNP.log` on your Vera). This will require you
+1. After operating for a while, I'll ask you to email me your log file (`/etc/cmh/LuaUPnP.log` on your Vera). This will require you
to log in to your Vera directly with ssh, or use the Vera's native "write log to USB drive" function, or use one of the many
log capture scripts that's available.
+1. Don't forget to turn TestMode off (0) when finished.
-Above all, I ask that you please be patient. You probably already know that it an be challenging at times to figure out
-what's going on in your Vera's head. It's no different for developers.
+Above all, I ask that you please be patient. You probably already know that it can be frustrating at times to figure out
+what's going on in your Vera's head. It's no different for developers--it can be a challenging development environment
+when the Vera is sitting in front of you, and moreso when dealing with someone else's Vera at a distance.
## FAQ ##
From 57adb4c3c90db62aca15f89b39d5f3e1291bb6f3 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 27 Dec 2016 09:57:00 -0500
Subject: [PATCH 28/49] Smooth out language
---
README.md | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index ffc6e77..449059f 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,10 @@ DeusExMachinaII: The Vacation Plugin
## Introduction ##
-DeusExMachina is a plugin for the MiCasaVerde Vera home automation system. It takes over your house while you're away on vacation by creating a ghost that moves from room to room, turning on and off lights. Simply specify the lights you want to have controlled by the plugin, specify a "Lights Out" time when lights will begin to turn off, and come sundown DeusExMachina will take over.
+DeusExMachina is a plugin for the MiOS home automation operating system used on MiCasaVerde Vera gateway/controllers.
+It takes over your house while you're away on vacation by creating a ghost that moves from room to room, turning on and off lights.
+Simply specify the lights you want to have controlled by the plugin, specify a "Lights Out" time when lights will begin to
+turn off, and come sundown DeusExMachina will take over.
There are currently two versions of Deus Ex Machina available:
@@ -168,13 +171,13 @@ when the Vera is sitting in front of you, and moreso when dealing with someone e
Vera and make sure everything is correct for the Vera's physical location. Remember that in
the western hemisphere (North, Central & South America, principally) your longitude will
be a negative number. If you are below the equator, latitude will be negative. If you're not
- sure what your latitude/longitude are, use a site like [MyGeoPosition.com](http://mygeoposition.com).
+ sure what your latitude/longitude are, use a site like MyGeoPosition.com .
If you make any changes to your time or location configuration, restart your Vera.
You're not waiting long enough. DEMII doesn't instantly jump into action at sunset, it employs its
configured cycle delays as well, so cycling will usually begin sometime after sunset, up to the
configured maximum cycle delay (30 minutes by default).
Your house mode isn't "active." If you've configured DEMII to operate only in certain house modes,
- make sure you're in one of those modes, otherwise DEMII will sit, even though it's enabled.
+ make sure you're in one of those modes, otherwise DEMII will just sit, even though it's enabled.
@@ -190,19 +193,20 @@ when the Vera is sitting in front of you, and moreso when dealing with someone e
What's the difference between House Mode and Enabled/Disabled? Can I just use House Mode to enable and disable DEMII?
The enabled/disabled state of DEMII is the "big red button" for its operation. If you configure DEMII to only run in certain
- house modes, then you can theoretically leave DEMII enabled all the time, as it will operate (cycle lights) only when a
- selected house mode is active. But, some people don't use the House Modes for various reasons, so having a master switch
+ house modes, then you can theoretically leave DEMII enabled all the time, as it will only operate (cycle lights) when a
+ selected house mode is active. But, some people don't use House Modes for various reasons, so having a master switch
for DEMII is necessary.
I have a feature request. Will you implement it?
Absolutely definitely maybe. I'm willing to listen to what you want to do. But, keep in mind, nobody's getting rich writing Vera
plugins, and I do other things that put food on my table. And, what seems like a good idea to you may be just that: a good idea for
- the way _you_ want to use it. The more generally applicable your request is, the higher the likelihood that I'll entertain it. What
+ the way you want to use it. The more generally applicable your request is, the higher the likelihood that I'll entertain it. What
I don't want to do is over-complicate this plug-in so it begins to rival PLEG for size and weight (no disrespect intended there at
all--I'm a huge PLEG fan and use it extensively, but, dang). DEMII really has a simple job: make lights go on and off to cast a serious
shadow of doubt in the mind of some knucklehead who might be thinking your house is empty and ripe for his picking. In any case,
the best way to give me feature requests is to open up an issue (if you have a list, one issue per feature, please) in the
- [GitHub repository](https://github.com/toggledbits/DeusExMachina/issues). Second best is sending me a message via the MCV forums
- (I'm user `rigpapa`).
+ GitHub repository .
+ Second best is sending me a message via the MCV forums (I'm user `rigpapa`).
+
From 2b9bf1497e52d1e0ec7afd70cb965abaf0da142b Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 27 Dec 2016 09:58:59 -0500
Subject: [PATCH 29/49] Fix URN
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 449059f..6f2fc19 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ you simply use the SetTarget action to enable (newTargetValue=1) or disable (new
The MiOS GUI for devices and scenes takes care of this for you in its code; if scripting in Lua, you simply do this:
```
-luup.call_action("urn:upnp-org-serviceId:SwitchPower1", "SetTarget", { newTargetValue = "0|1" }, pluginDeviceId)
+luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", { newTargetValue = "0|1" }, pluginDeviceId)
```
### Triggers ###
From 33cd37c0d173ba870113f64ef386325c9acdd50f Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 6 Jan 2017 19:15:40 -0500
Subject: [PATCH 30/49] Remove temp debug function
---
J_DeusExMachinaII1_UI7.js | 6 ------
1 file changed, 6 deletions(-)
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index a80f5c8..a7c8dd5 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -19,12 +19,6 @@ var DeusExMachinaII = (function(api) {
api.registerEventHandler('on_ui_cpanel_before_close', myModule, 'onBeforeCpanelClose');
}
- function safe(obj) {
- if (obj === undefined) return "undefined"
- else if (obj instanceof jQuery || obj.constructor.prototype.jquery) return "jQuery[" + obj.length + "]";
- else return '(' + typeof(obj) + ')' + obj.toString();
- }
-
function isDimmer(devid) {
var v = api.getDeviceState( devid, "urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus" );
if (v === undefined || v === false) return false;
From 9e06074561aa2b14504996ca4af6e44894e53350 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 10:43:58 -0500
Subject: [PATCH 31/49] Fix argument list
---
S_DeusExMachinaII1.xml | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/S_DeusExMachinaII1.xml b/S_DeusExMachinaII1.xml
index 9a8b1de..a59ae9c 100644
--- a/S_DeusExMachinaII1.xml
+++ b/S_DeusExMachinaII1.xml
@@ -40,9 +40,11 @@
SetEnabled
- NewEnabledValue
- Enabled
- in
+
+ NewEnabledValue
+ Enabled
+ in
+
From 65abf3b50d09296efe4189c943d6ca378bb6062d Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 12:59:23 -0500
Subject: [PATCH 32/49] Add action to get plugin version string
---
S_DeusExMachinaII1.xml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/S_DeusExMachinaII1.xml b/S_DeusExMachinaII1.xml
index a59ae9c..59b8d20 100644
--- a/S_DeusExMachinaII1.xml
+++ b/S_DeusExMachinaII1.xml
@@ -47,5 +47,15 @@
+
+ GetPluginVersion
+
+
+ ResultVersion
+ out
+ TempStorage
+
+
+
\ No newline at end of file
From 5a17b1f781a91b873a3094b51a0dfd3688310e77 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 12:59:49 -0500
Subject: [PATCH 33/49] Add action to get plugin version string
---
I_DeusExMachinaII1.xml | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index d647e1d..29450df 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -19,7 +19,7 @@
urn:upnp-org:serviceId:SwitchPower1
SetTarget
-
+
local newTargetValue = lul_settings.newTargetValue or "0"
luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newTargetValue, lul_device)
if (newTargetValue == "1") then
@@ -27,12 +27,26 @@
else
demII.deusDisable()
end
-
+
+
+
+ urn:upnp-org:serviceId:SwitchPower1
+ GetTarget
+
+ luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Target", lul_device)
+
+
+
+ urn:upnp-org:serviceId:SwitchPower1
+ GetStatus
+
+ luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Status", lul_device)
+
urn:toggledbits-com:serviceId:DeusExMachinaII1
SetEnabled
-
+
local newEnabledValue = lul_settings.NewEnabledValue or "0"
luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newEnabledValue, lul_device)
if (newEnabledValue == "1") then
@@ -40,7 +54,18 @@
else
demII.deusDisable()
end
-
+
+
+
+ urn:toggledbits-com:serviceId:DeusExMachinaII1
+ GetPluginVersion
+
+ -- Ugly hack. Luup seems to only be able to return values from related state variables (see S_), so use a temp
+ -- one to store the result we want to pass back. Blech. C'mon guys. Amateur hour. Add an extra return argument
+ -- for a table of return values or something, please?
+ luup.variable_set("urn:toggledbits-com:serviceId:DeusExMachinaII1", "TempStorage", demII.getVersion(), lul_device)
+ return true
+
From f329697bbe8e043b3babdd8feba7ea8ba6b29d8e Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 13:00:12 -0500
Subject: [PATCH 34/49] Comment out debug in prep for release
---
J_DeusExMachinaII1_UI7.js | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index a7c8dd5..0961daf 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -12,7 +12,7 @@ var DeusExMachinaII = (function(api) {
var sceneNamesById = [];
function onBeforeCpanelClose(args) {
- console.log('handler for before cpanel close');
+ // console.log('handler for before cpanel close');
}
function init() {
@@ -54,12 +54,12 @@ var DeusExMachinaII = (function(api) {
});
jQuery('.controlled-scenes').each( function( ix, obj ) {
var devid = jQuery(obj).attr('id');
- console.log('updateControlledList: handling scene pair ' + devid);
+ // console.log('updateControlledList: handling scene pair ' + devid);
controlled.push(devid);
});
var s = controlled.join(',');
- console.log('Updating controlled list to ' + s);
+ // console.log('Updating controlled list to ' + s);
api.setDeviceStatePersistent(deusDevice, serviceId, "Devices", s, 0);
}
@@ -347,7 +347,7 @@ var DeusExMachinaII = (function(api) {
// Push generated HTML to page
api.setCpanelContent(html);
-
+
// Restore time field
var time = "23:59";
var timeMins = parseInt(api.getDeviceState(deusDevice, serviceId, "LightsOut"));
@@ -381,13 +381,11 @@ var DeusExMachinaII = (function(api) {
var id = jQuery(obj).attr('id');
id = id.substr(6);
jQuery('input#device'+id+':checked').each( function() {
-console.log('input#device'+id+' is checked, enabling slider...');
// Corresponding checked checkbox, enable slider.
jQuery(obj).slider("option", "disabled", false);
var ix = DeusExMachinaII.findControlledDevice(id);
if (ix >= 0) {
var info = DeusExMachinaII.getControlled(ix);
-console.log('found for ' + info.type + ' ' + info.device + ' with value=' + info.value + ', raw=' + info.raw + ", restoring slider value...");
jQuery(obj).slider("option", "value", info.type == "device" ? info.value : 100);
}
});
From ed641ffa2c56079dd95ac79638e5b5a3cc6addbb Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 13:01:13 -0500
Subject: [PATCH 35/49] Support for action to get plugin version string
---
L_DeusExMachinaII1.lua | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index 3e54f97..81b8a5c 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -1,8 +1,10 @@
module("L_DeusExMachinaII1", package.seeall)
-local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
+local _VERSION = "2.4RC4"
local DEMVERSION = 20400
+local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
+
local SWITCH_TYPE = "urn:schemas-upnp-org:device:BinaryLight:1"
local SWITCH_SID = "urn:upnp-org:serviceId:SwitchPower1"
local DIMMER_TYPE = "urn:schemas-upnp-org:device:DimmableLight:1"
@@ -503,6 +505,11 @@ local function runOnce()
luup.variable_set(SID, "Version", DEMVERSION, lul_device)
end
+-- Return the plugin version string
+function getVersion()
+ return _VERSION
+end
+
-- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
function deusEnable()
setMessage("Enabling...")
@@ -532,7 +539,7 @@ end
-- Initialize.
function deusInit(deusDevice)
setMessage("Initializing...")
- luup.log("DeusExMachinaII:deusInit(): Version 2.4RC4 (2016-12-23), initializing...")
+ luup.log("DeusExMachinaII:deusInit(): Version " .. _VERSION .. ", initializing...")
-- One-time stuff
runOnce()
From 6668d415dc0a2c595f9034ae8732414ff1e3ba3c Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sat, 7 Jan 2017 13:26:24 -0500
Subject: [PATCH 36/49] Fix version for release
---
D_DeusExMachinaII1_UI7.json | 2 +-
L_DeusExMachinaII1.lua | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index a1081c6..7a0f00e 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -103,7 +103,7 @@
"left": "0",
"Label": {
"lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4RC4 2016-12-23
+ "text": "DeusExMachina II ver 2.4 2017-01-07
For documentation or to report bugs, please go to the DeusExMachina Github repository .
This plugin is offered for use as-is and without warranties of any kind. By using this plugin,
you agree to assume all risks in connection with its use without limitation."
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index 81b8a5c..2e81c5b 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -1,6 +1,6 @@
module("L_DeusExMachinaII1", package.seeall)
-local _VERSION = "2.4RC4"
+local _VERSION = "2.4"
local DEMVERSION = 20400
local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
From a1c73476856536a152fd8ad8f163b6cded7ab67d Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 8 Jan 2017 22:30:39 -0500
Subject: [PATCH 37/49] Include name of device in debug output (helps with
identification)
---
L_DeusExMachinaII1.lua | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index 2e81c5b..b7ad1dd 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -345,19 +345,19 @@ local function targetControl(targetid, turnOn)
-- Level for all types is 0 if turning device off
if not turnOn then lvl = 0 end
if luup.devices[targetid] == nil then
- -- Device doesn't exist (user deleted, etc.). PHR??? Should remove from Devices state variable.
+ -- Device doesn't exist (user deleted, etc.). Remove from Devices state variable.
luup.log("DeusExMachinaII:targetControl(): device " .. tostring(targetid) .. " not found in luup.devices")
removeTarget(targetid)
return
end
if luup.device_supports_service(DIMMER_SID, targetid) then
-- Handle as Dimming1
- debug("targetControl(): handling %1 as dimmmer, set load level to %2", targetid, lvl)
+ debug("targetControl(): handling %1 (%3) as dimmmer, set load level to %2", targetid, lvl, luup.devices[targetid].description)
luup.call_action(DIMMER_SID, "SetLoadLevelTarget", {newLoadlevelTarget=lvl}, targetid) -- note odd case inconsistency
elseif luup.device_supports_service(SWITCH_SID, targetid) then
-- Handle as SwitchPower1
if turnOn then lvl = 1 end
- debug("targetControl(): handling %1 as switch, setting target to %2", targetid, lvl)
+ debug("targetControl(): handling %1 (%3) as switch, set target to %2", targetid, lvl, luup.devices[targetid].description)
luup.call_action(SWITCH_SID, "SetTarget", {newTargetValue=lvl}, targetid)
else
luup.log("DeusExMachinaII:targetControl(): don't know how to control target " .. tostring(targetid))
From 94f09929c54f3384a27abf723a6c3240854d5d67 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 8 Jan 2017 22:31:18 -0500
Subject: [PATCH 38/49] Reformat
---
D_DeusExMachinaII1_UI7.json | 537 +++++++++++++++++++-----------------
1 file changed, 285 insertions(+), 252 deletions(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index 7a0f00e..ce95038 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -1,258 +1,291 @@
-{
- "default_icon": "https://dtabq7xg0g1t1.cloudfront.net/deusII.png",
- "doc_url": {
- "doc_language": 1,
- "doc_manual": 1,
- "doc_version": 1,
- "doc_platform": 0,
- "doc_page": "devices"
- },
- "state_icons": [{
- "img": "https://dtabq7xg0g1t1.cloudfront.net/deus-red.png",
- "conditions": [{
- "service": "urn:upnp-org:serviceId:SwitchPower1",
- "variable": "Status",
- "operator": "==",
- "value": 0,
- "subcategory_num": 0
- }]
- }, {
- "img": "https://dtabq7xg0g1t1.cloudfront.net/deus-green.png",
- "conditions": [{
- "service": "urn:upnp-org:serviceId:SwitchPower1",
- "variable": "Status",
- "operator": "==",
- "value": 1,
- "subcategory_num": 0
- }]
- }],
- "x": "2",
- "y": "4",
- "inScene": "1",
- "ToggleButton": "1",
- "Tabs": [{
- "Label": {
- "lang_tag": "ui7_tabname_control",
- "text": "Control"
- },
- "Position": "0",
- "TabType": "flash",
- "top_navigation_tab": 1,
- "ControlGroup": [{
- "id": "1",
- "isSingle": "1",
- "scenegroup": "1"
- }],
- "SceneGroup": [{
- "id": "1",
- "top": "2",
- "left": "0",
- "x": "2",
- "y": "1"
- }],
- "Control": [{
- "ControlGroup": "1",
- "ControlType": "multi_state_button",
- "top": "0",
- "left": "1",
- "states": [{
- "Label": {
- "lang_tag": "ui7_cmd_on",
- "text": "On"
- },
- "ControlGroup": 1,
- "Display": {
- "Service": "urn:upnp-org:serviceId:SwitchPower1",
- "Variable": "Status",
- "Value": "1"
- },
- "Command": {
- "Service": "urn:upnp-org:serviceId:SwitchPower1",
- "Action": "SetTarget",
- "Parameters": [{
- "Name": "newTargetValue",
- "Value": "1"
- }]
- },
- "ControlCode": "power_on"
- }, {
- "Label": {
- "lang_tag": "ui7_cmd_off",
- "text": "Off"
- },
- "ControlGroup": 1,
- "Display": {
- "Service": "urn:upnp-org:serviceId:SwitchPower1",
- "Variable": "Status",
- "Value": "0"
- },
- "Command": {
- "Service": "urn:upnp-org:serviceId:SwitchPower1",
- "Action": "SetTarget",
- "Parameters": [{
- "Name": "newTargetValue",
- "Value": "0"
- }]
- },
- "ControlCode": "power_off"
- }],
- "ControlCode": "dem_statecontrol"
- }, {
- "ControlType": "label",
- "top": "0",
- "left": "0",
- "Label": {
- "lang_tag": "dem_about",
- "text": "DeusExMachina II ver 2.4 2017-01-07
- For documentation or to report bugs, please go to the DeusExMachina Github repository .
- This plugin is offered for use as-is and without warranties of any kind. By using this plugin,
- you agree to assume all risks in connection with its use without limitation."
- },
- "Display": {
- "Top": "80",
- "Left": "0",
- "Width": "200",
- "Height": "24"
+{
+ "default_icon":"https://dtabq7xg0g1t1.cloudfront.net/deusII.png",
+ "state_icons":[
+ {
+ "img":"https://dtabq7xg0g1t1.cloudfront.net/deus-red.png",
+ "conditions":[
+ {
+ "service":"urn:upnp-org:serviceId:SwitchPower1",
+ "variable":"Status",
+ "operator":"==",
+ "value":0,
+ "subcategory_num":0
+ }
+ ]
+ },
+ {
+ "img":"https://dtabq7xg0g1t1.cloudfront.net/deus-green.png",
+ "conditions":[
+ {
+ "service":"urn:upnp-org:serviceId:SwitchPower1",
+ "variable":"Status",
+ "operator":"==",
+ "value":1,
+ "subcategory_num":0
}
- }, {
- "ControlType": "variable",
- "top": "0",
- "left": "0",
- "Display": {
- "Service": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "Variable": "Message",
- "Top": "16",
- "Left": "120",
- "Width": "200",
- "Height": "20"
+ ]
+ }
+ ],
+ "x":"2",
+ "y":"4",
+ "inScene":"1",
+ "ToggleButton":"1",
+ "Tabs":[
+ {
+ "Label":{
+ "lang_tag":"ui7_tabname_control",
+ "text":"Control"
+ },
+ "Position":"0",
+ "TabType":"flash",
+ "top_navigation_tab":1,
+ "ControlGroup":[
+ {
+ "id":"1",
+ "isSingle":"1",
+ "scenegroup":"1"
}
- }]
- }, {
- "Label": {
- "lang_tag": "configure",
- "text": "Configure"
- },
- "Position": "1",
- "TabType": "javascript",
- "ScriptName": "J_DeusExMachinaII1_UI7.js",
- "Function": "DeusExMachinaII.configureDeus"
- }, {
- "Label": {
- "lang_tag": "ui7_advanced",
- "text": "Advanced"
- },
- "Position": "2",
- "TabType": "javascript",
- "ScriptName": "shared.js",
- "Function": "advanced_device"
- }],
- "sceneList": {
- "group_1": {
- "cmd_1": {
- "label": "ON",
- "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
- "action": "SetTarget",
- "arguments": {
- "newTargetValue": "1"
- },
- "display": {
- "service": "urn:upnp-org:serviceId:SwitchPower1",
- "variable": "Status",
- "value": "1"
- }
+ ],
+ "SceneGroup":[
+ {
+ "id":"1",
+ "top":"2",
+ "left":"0",
+ "x":"2",
+ "y":"1"
+ }
+ ],
+ "Control":[
+ {
+ "ControlGroup":"1",
+ "ControlType":"multi_state_button",
+ "top":"0",
+ "left":"1",
+ "states":[
+ {
+ "Label":{
+ "lang_tag":"ui7_cmd_on",
+ "text":"On"
+ },
+ "ControlGroup":1,
+ "Display":{
+ "Service":"urn:upnp-org:serviceId:SwitchPower1",
+ "Variable":"Status",
+ "Value":"1"
+ },
+ "Command":{
+ "Service":"urn:upnp-org:serviceId:SwitchPower1",
+ "Action":"SetTarget",
+ "Parameters":[
+ {
+ "Name":"newTargetValue",
+ "Value":"1"
+ }
+ ]
+ },
+ "ControlCode":"power_on"
+ },
+ {
+ "Label":{
+ "lang_tag":"ui7_cmd_off",
+ "text":"Off"
+ },
+ "ControlGroup":1,
+ "Display":{
+ "Service":"urn:upnp-org:serviceId:SwitchPower1",
+ "Variable":"Status",
+ "Value":"0"
+ },
+ "Command":{
+ "Service":"urn:upnp-org:serviceId:SwitchPower1",
+ "Action":"SetTarget",
+ "Parameters":[
+ {
+ "Name":"newTargetValue",
+ "Value":"0"
+ }
+ ]
+ },
+ "ControlCode":"power_off"
+ }
+ ]
+ },
+ {
+ "ControlType":"label",
+ "top":"0",
+ "left":"0",
+ "Label":{
+ "lang_tag":"dem_about",
+ "text":"DeusExMachina II ver 2.4 2017-01-07 For documentation or to report bugs, please go to the DeusExMachina Github repository . This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation."
+ },
+ "Display":{
+ "Top":"80",
+ "Left":"0",
+ "Width":"200",
+ "Height":"24"
+ }
},
- "cmd_2": {
- "label": "OFF",
- "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
- "action": "SetTarget",
- "arguments": {
- "newTargetValue": "0"
- },
- "display": {
- "service": "urn:upnp-org:serviceId:SwitchPower1",
- "variable": "Status",
- "value": "0"
- }
+ {
+ "ControlType":"variable",
+ "top":"0",
+ "left":"0",
+ "Display":{
+ "Service":"urn:toggledbits-com:serviceId:DeusExMachinaII1",
+ "Variable":"Message",
+ "Top":"16",
+ "Left":"120",
+ "Width":"200",
+ "Height":"20"
+ }
}
- }
- },
- "eventList2": [{
- "id": 1,
- "label": {
- "lang_tag": "dem_enabledisable",
- "text": "Is enabled or disabled"
- },
- "serviceId": "urn:upnp-org:serviceId:SwitchPower1",
- "argumentList": [{
- "id": 1,
- "dataType": "boolean",
- "defaultValue": "0",
- "allowedValueList": [{
- "Disabled": "0",
- "HumanFriendlyText": {
- "lang_tag": "dem_disabled",
- "text": "_DEVICE_NAME_ is disabled"
- }
- }, {
- "Enabled": "1",
- "HumanFriendlyText": {
- "lang_tag": "dem_enabled",
- "text": "_DEVICE_NAME_ is enabled"
- }
- }],
- "name": "Status",
- "comparisson": "=",
- "prefix": {
- "lang_tag": "dem_when",
- "text": "When"
+ ]
+ },
+ {
+ "Label":{
+ "lang_tag":"configure",
+ "text":"Configure"
+ },
+ "Position":"1",
+ "TabType":"javascript",
+ "ScriptName":"J_DeusExMachinaII1_UI7.js",
+ "Function":"DeusExMachinaII.configureDeus"
+ },
+ {
+ "Label":{
+ "lang_tag":"ui7_advanced",
+ "text":"Advanced"
+ },
+ "Position":"2",
+ "TabType":"javascript",
+ "ScriptName":"shared.js",
+ "Function":"advanced_device"
+ }
+ ],
+ "sceneList":{
+ "group_1":{
+ "cmd_1":{
+ "label":"ON",
+ "serviceId":"urn:upnp-org:serviceId:SwitchPower1",
+ "action":"SetTarget",
+ "arguments":{
+ "newTargetValue":"1"
},
- "suffix": {}
- }]
- }, {
- "id": 2,
- "label": {
- "lang_tag": "dem_opstate",
- "text": "The operating mode changes"
- },
- "serviceId": "urn:toggledbits-com:serviceId:DeusExMachinaII1",
- "argumentList": [{
- "id": 1,
- "dataType": "ui1",
- "defaultValue": "0",
- "allowedValueList": [{
- "value": "0",
- "HumanFriendlyText": {
- "lang_tag": "dem_standby",
- "text": "Standby"
- }
- }, {
- "value": "1",
- "HumanFriendlyText": {
- "lang_tag": "dem_ready",
- "text": "Ready"
- }
- }, {
- "value": "2",
- "HumanFriendlyText": {
- "lang_tag": "dem_cycle",
- "text": "Cycling"
- }
- }, {
- "value": "3",
- "HumanFriendlyText": {
- "lang_tag": "dem_shutoff",
- "text": "Shut-off"
- }
- }],
- "name": "State",
- "comparisson": "=",
- "prefix": {
- "lang_tag": "dem_when",
- "text": "to"
+ "display":{
+ "service":"urn:upnp-org:serviceId:SwitchPower1",
+ "variable":"Status",
+ "value":"1"
+ }
+ },
+ "cmd_2":{
+ "label":"OFF",
+ "serviceId":"urn:upnp-org:serviceId:SwitchPower1",
+ "action":"SetTarget",
+ "arguments":{
+ "newTargetValue":"0"
},
- "suffix": {}
- }]
- }],
- "DeviceType": "urn:schemas-toggledbits-com:device:DeusExMachinaII:1",
- "device_type": "urn:schemas-toggledbits-com:device:DeusExMachinaII:1"
-}
+ "display":{
+ "service":"urn:upnp-org:serviceId:SwitchPower1",
+ "variable":"Status",
+ "value":"0"
+ }
+ }
+ }
+ },
+ "eventList2":[
+ {
+ "id":1,
+ "label":{
+ "lang_tag":"dem_enabledisable",
+ "text":"Is enabled or disabled"
+ },
+ "serviceId":"urn:upnp-org:serviceId:SwitchPower1",
+ "argumentList":[
+ {
+ "id":1,
+ "dataType":"boolean",
+ "defaultValue":"0",
+ "allowedValueList":[
+ {
+ "Disabled":"0",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_disabled",
+ "text":"_DEVICE_NAME_ is disabled"
+ }
+ },
+ {
+ "Enabled":"1",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_enabled",
+ "text":"_DEVICE_NAME_ is enabled"
+ }
+ }
+ ],
+ "name":"Status",
+ "comparisson":"=",
+ "prefix":{
+ "lang_tag":"dem_when",
+ "text":"When"
+ },
+ "suffix":{
+
+ }
+ }
+ ]
+ },
+ {
+ "id":2,
+ "label":{
+ "lang_tag":"dem_opstate",
+ "text":"The operating mode changes"
+ },
+ "serviceId":"urn:toggledbits-com:serviceId:DeusExMachinaII1",
+ "argumentList":[
+ {
+ "id":1,
+ "dataType":"ui1",
+ "defaultValue":"0",
+ "allowedValueList":[
+ {
+ "value":"0",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_standby",
+ "text":"Standby"
+ }
+ },
+ {
+ "value":"1",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_ready",
+ "text":"Ready"
+ }
+ },
+ {
+ "value":"2",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_cycle",
+ "text":"Cycling"
+ }
+ },
+ {
+ "value":"3",
+ "HumanFriendlyText":{
+ "lang_tag":"dem_shutoff",
+ "text":"Shut-off"
+ }
+ }
+ ],
+ "name":"State",
+ "comparisson":"=",
+ "prefix":{
+ "lang_tag":"dem_when",
+ "text":"to"
+ },
+ "suffix":{
+
+ }
+ }
+ ]
+ }
+ ],
+ "device_type":"urn:schemas-toggledbits-com:device:DeusExMachinaII:1"
+}
\ No newline at end of file
From d766a94c9b9b4a3495ff0fea48982f49178dcf19 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Mon, 9 Jan 2017 18:14:42 -0500
Subject: [PATCH 39/49] 1) Fix call_delay use so that delayed execution occurs
async and the current thread is released. Change order of initialization
around those calls to make sure all state is initialized even if the call
doesn't return (redundant fix for the same issue: some state variables were
being set after call_delay, but the old usage may not have allowed the call
to return before a Luup crash, so the inits would never get done). 2) Fix
device number handling. The 'lul_device' pseudo-global is only available in a
limited context, and most of L_ isn't it. While Luup behaves well when passed
nil device numbers to many calls, it's a bug and would be sloppy coding IMHO
to leave it wrong even though it still works right (and not just sloppy, but
misleading to anyone else reading the code).
---
I_DeusExMachinaII1.xml | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 29450df..235d189 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -7,11 +7,11 @@
-- Original code and releases 1.x by Andy Lintner (beowulfe) Version 2.0 and beyond by Patrick Rigney (rigpapa/toggledbits).
-- A big thanks to Andy for passing the torch so that this great plug-in can live on.
-- -------------------------------------------------------------------------------------------------------------------------
- function startupDeusExMachinaII1()
+ function startupDeusExMachinaII1(deusDevice)
luup.log("DeusExMachinaII STARTUP!")
demII = require("L_DeusExMachinaII1")
deusStep = demII.deusStep
- demII.deusInit()
+ demII.deusInit(deusDevice)
end
startupDeusExMachinaII1
@@ -21,11 +21,10 @@
SetTarget
local newTargetValue = lul_settings.newTargetValue or "0"
- luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newTargetValue, lul_device)
if (newTargetValue == "1") then
- demII.deusEnable()
+ demII.deusEnable(lul_device)
else
- demII.deusDisable()
+ demII.deusDisable(lul_device)
end
@@ -50,9 +49,9 @@
local newEnabledValue = lul_settings.NewEnabledValue or "0"
luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Target", newEnabledValue, lul_device)
if (newEnabledValue == "1") then
- demII.deusEnable()
+ demII.deusEnable(lul_device)
else
- demII.deusDisable()
+ demII.deusDisable(lul_device)
end
From 5d8f3d1ecfb9780044647398a8337b7ff7dd2e08 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Mon, 9 Jan 2017 18:19:29 -0500
Subject: [PATCH 40/49] 1) Fix call_delay use so that delayed execution occurs
async and the current thread is released. Change order of initialization
around those calls to make sure all state is initialized even if the call
doesn't return (redundant fix for the same issue: some state variables were
being set after call_delay, but the old usage may not have allowed the call
to return before a Luup crash, so the inits would never get done). 2) Fix
device number handling. The 'lul_device' pseudo-global is only available in a
limited context, and most of L_ isn't it. While Luup behaves well when passed
nil device numbers to many calls, it's a bug and would be sloppy coding IMHO
to leave it wrong even though it still works right (and not just sloppy, but
misleading to anyone else reading the code).
---
L_DeusExMachinaII1.lua | 155 ++++++++++++++++++++++++-----------------
1 file changed, 90 insertions(+), 65 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index b7ad1dd..bce2a8b 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -1,6 +1,6 @@
module("L_DeusExMachinaII1", package.seeall)
-local _VERSION = "2.4"
+local _VERSION = "2.4RC5"
local DEMVERSION = 20400
local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
@@ -16,12 +16,11 @@ local STATE_CYCLE = 2
local STATE_SHUTDOWN = 3
local runStamp = 0
-
-local debugMode = false
+local debugMode = true
local function debug(...)
if debugMode then
- local str = "DeusExMachinaII1:" .. arg[1]
+ local str = "DeusExMachinaII1(dbg):" .. arg[1]
local ipos = 1
while true do
local i, j, n
@@ -42,21 +41,22 @@ local function debug(...)
end
local function checkVersion()
- local ui7Check = luup.variable_get(SID, "UI7Check", lul_device) or ""
+ local ui7Check = luup.variable_get(SID, "UI7Check", luup.device) or ""
if ui7Check == "" then
- luup.variable_set(SID, "UI7Check", "false", lul_device)
+ luup.variable_set(SID, "UI7Check", "false", luup.device)
ui7Check = "false"
end
if ( luup.version_branch == 1 and luup.version_major == 7 and ui7Check == "false" ) then
- luup.variable_set(SID, "UI7Check", "true", lul_device)
- luup.attr_set("device_json", "D_DeusExMachinaII1_UI7.json", lul_device)
+ luup.variable_set(SID, "UI7Check", "true", luup.device)
+ luup.attr_set("device_json", "D_DeusExMachinaII1_UI7.json", luup.device)
luup.reload()
end
end
-- Get numeric variable, or return default value if not set or blank
-local function getVarNumeric( name, dflt )
- local s = luup.variable_get(SID, name, lul_device)
+local function getVarNumeric( name, dflt, dev )
+ if dev == nil then dev = luup.device end
+ local s = luup.variable_get(SID, name, dev)
if (s == nil or s == "") then return dflt end
s = tonumber(s, 10)
if (s == nil) then return dflt end
@@ -86,12 +86,12 @@ local function timeToString(t)
end
local function setMessage(s)
- luup.variable_set(SID, "Message", s or "", lul_device)
+ luup.variable_set(SID, "Message", s or "", luup.device)
end
-- Shortcut function to return state of SwitchPower1 Status variable
local function isEnabled()
- local s = luup.variable_get(SWITCH_SID, "Status", lul_device)
+ local s = luup.variable_get(SWITCH_SID, "Status", luup.device)
if (s == nil or s == "") then return false end
return (s ~= "0")
end
@@ -163,7 +163,7 @@ end
-- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
-- to minutes-since-midnight units.
local function isBedtime()
- local testing = getVarNumeric("TestMode", 0)
+ local testing = getVarNumeric("TestMode", 0, luup.device)
if (testing ~= 0) then
luup.log('DeusExMachinaII:isBedtime(): TestMode is on')
debugMode = true
@@ -171,7 +171,7 @@ local function isBedtime()
-- Establish the lights-out time
local bedtime = 1439 -- that's 23:59 in minutes since midnight (default)
- local bedtime_tmp = luup.variable_get(SID, "LightsOut", lul_device)
+ local bedtime_tmp = luup.variable_get(SID, "LightsOut", luup.device)
if (bedtime_tmp ~= nil) then
bedtime_tmp = tonumber(bedtime_tmp,10)
if (bedtime_tmp >= 0 and bedtime_tmp < 1440) then bedtime = bedtime_tmp end
@@ -228,7 +228,7 @@ end
-- Return true if a specified scene has been run (i.e. on the list)
local function isSceneOn(spec)
- local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ local stateList = luup.variable_get(SID, "ScenesRunning", luup.device) or ""
for i in string.gfind(stateList, "[^,]+") do
if (i == spec) then return true end
end
@@ -237,7 +237,7 @@ end
-- Mark or unmark a scene as having been run
local function updateSceneState(spec, isOn)
- local stateList = luup.variable_get(SID, "ScenesRunning", lul_device) or ""
+ local stateList = luup.variable_get(SID, "ScenesRunning", luup.device) or ""
local i
local t = {}
for i in string.gfind(stateList, "[^,]+") do
@@ -250,7 +250,7 @@ local function updateSceneState(spec, isOn)
end
stateList = ""
for i in pairs(t) do stateList = stateList .. "," .. tostring(i) end
- luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), lul_device)
+ luup.variable_set(SID, "ScenesRunning", string.sub(stateList, 2, -1), luup.device)
end
-- Run "final" scene, if defined. This scene is run after all other targets have been
@@ -265,7 +265,7 @@ end
-- Get the list of targets from our device state, parse to table of targets.
local function getTargetList()
- local s = luup.variable_get(SID, "Devices", lul_device) or ""
+ local s = luup.variable_get(SID, "Devices", luup.device) or ""
return split(s)
end
@@ -276,7 +276,7 @@ local function removeTarget(target, tlist)
for i = 1,table.getn(tlist) do
if tostring(target) == tlist[i] then
table.remove(tlist, i)
- luup.variable_set(SID, "Devices", table.concat(tlist, ","), lul_device)
+ luup.variable_set(SID, "Devices", table.concat(tlist, ","), deusDevuce)
return true
end
end
@@ -409,7 +409,7 @@ end
local function clearLights()
local devs, count
devs, count = getTargetList()
- luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
+ luup.variable_set(SID, "State", STATE_SHUTDOWN, luup.device)
while count > 0 do
targetControl(devs[count], false)
count = count - 1
@@ -421,7 +421,7 @@ end
-- takes place. For us, that means looking to see if an older version of Deus is still
-- installed, and copying its config into our new config. Then disable the old Deus.
local function runOnce()
- local s = luup.variable_get(SID, "Devices", lul_device)
+ local s = luup.variable_get(SID, "Devices", luup.device)
if (s == nil) then
luup.log("DeusExMachinaII:runOnce(): Devices variable not found, setting up new instance...")
-- See if there are variables from older version of DEM
@@ -442,8 +442,8 @@ local function runOnce()
s = luup.variable_get(oldsid, "LightsOutTime", olddev)
if (s ~= nil) then
local n = tonumber(s,10) / 60000
- luup.variable_set(SID, "LightsOut", n, lul_device)
- deleteVar("LightsOutTime", lul_device)
+ luup.variable_set(SID, "LightsOut", n, luup.device)
+ deleteVar("LightsOutTime", luup.device)
end
s = luup.variable_get(oldsid, "controlCount", olddev)
if (s ~= nil) then
@@ -457,25 +457,25 @@ local function runOnce()
end
end
devList = table.concat(t, ",")
- deleteVar("controlCount", lul_device)
+ deleteVar("controlCount", luup.device)
end
-- Finally, turn off old Deus
luup.call_action(oldsid, "SetEnabled", { NewEnabledValue = "0" }, olddev)
end
- luup.variable_set(SID, "Devices", devList, lul_device)
+ luup.variable_set(SID, "Devices", devList, luup.device)
-- Set up some other default config
- luup.variable_set(SID, "MinCycleDelay", "300", lul_device)
- luup.variable_set(SID, "MaxCycleDelay", "1800", lul_device)
- luup.variable_set(SID, "MinOffDelay", "60", lul_device)
- luup.variable_set(SID, "MaxOffDelay", "300", lul_device)
- luup.variable_set(SID, "LightsOut", 1439, lul_device)
- luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
- luup.variable_set(SID, "Enabled", "0", lul_device, true)
- luup.variable_set(SID, "Version", DEMVERSION, lul_device)
- luup.variable_set(SWITCH_SID, "Status", "0", lul_device, true)
- luup.variable_set(SWITCH_SID, "Target", "0", lul_device, true)
+ luup.variable_set(SID, "MinCycleDelay", "300", luup.device)
+ luup.variable_set(SID, "MaxCycleDelay", "1800", luup.device)
+ luup.variable_set(SID, "MinOffDelay", "60", luup.device)
+ luup.variable_set(SID, "MaxOffDelay", "300", luup.device)
+ luup.variable_set(SID, "LightsOut", 1439, luup.device)
+ luup.variable_set(SID, "MaxTargetsOn", 0, luup.device)
+ luup.variable_set(SID, "Enabled", "0", luup.device)
+ luup.variable_set(SID, "Version", DEMVERSION, luup.device)
+ luup.variable_set(SWITCH_SID, "Status", "0", luup.device)
+ luup.variable_set(SWITCH_SID, "Target", "0", luup.device)
end
-- Consider per-version changes.
@@ -483,63 +483,88 @@ local function runOnce()
if (s < 20300) then
-- v2.3: LightsOutTime (in milliseconds) deprecated, now using LightsOut (in minutes since midnight)
luup.log("DeusExMachinaII:runOnce(): updating config, version " .. tostring(s) .. " < 20300")
- s = luup.variable_get(SID, "LightsOut", lul_device)
+ s = luup.variable_get(SID, "LightsOut", luup.device)
if (s == nil) then
s = getVarNumeric("LightsOutTime") -- get pre-2.3 variable
if (s == nil) then
- luup.variable_set(SID, "LightsOut", 1439, lul_device) -- default 23:59
+ luup.variable_set(SID, "LightsOut", 1439, luup.device) -- default 23:59
else
- luup.variable_set(SID, "LightsOut", tonumber(s,10) / 60000, lul_device) -- conv ms to minutes
+ luup.variable_set(SID, "LightsOut", tonumber(s,10) / 60000, luup.device) -- conv ms to minutes
end
end
- deleteVar("LightsOutTime", lul_device)
+ deleteVar("LightsOutTime", luup.device)
end
if (s < 20400) then
- luup.variable_set(SID, "MaxTargetsOn", 0, lul_device)
+ -- v2.4: SwitchPower1 variables added. Follow previous plugin state in case of auto-update.
+ luup.log("DeusExMachinaII:runOnce(): updating config, version " .. tostring(s) .. " < 20400")
+ luup.variable_set(SID, "MaxTargetsOn", 0, luup.device)
local e = getVarNumeric("Enabled", 0)
- luup.variable_set(SWITCH_SID, "Status", e, lul_device, true)
- luup.variable_set(SWITCH_SID, "Target", e, lul_device, true)
+ luup.variable_set(SWITCH_SID, "Status", e, luup.device)
+ luup.variable_set(SWITCH_SID, "Target", e, luup.device)
end
- -- Update version last.
- luup.variable_set(SID, "Version", DEMVERSION, lul_device)
+ -- Update version state last.
+ if (s ~= DEMVERSION) then
+ luup.variable_set(SID, "Version", DEMVERSION, luup.device)
+ end
end
-- Return the plugin version string
function getVersion()
- return _VERSION
+ return _VERSION, DEMVERSION
end
-- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
function deusEnable()
+ luup.log("DeusExMachinaII:deusEnable(): enabling, luup.device=" .. tostring(luup.device))
+ luup.variable_set(SWITCH_SID, "Target", "1", luup.device)
+
setMessage("Enabling...")
- luup.log("DeusExMachinaII:deusEnable(): enabling...")
- luup.variable_set(SID, "ScenesRunning", "", lul_device) -- start with a clean slate
+
+ luup.variable_set(SWITCH_SID, "Status", "1", luup.device)
+ luup.variable_set(SID, "Enabled", "1", luup.device)
+
runStamp = os.time()
- luup.call_delay("deusStep", 1, runStamp, 1)
- luup.variable_set(SID, "Enabled", "1", lul_device)
- luup.variable_set(SWITCH_SID, "Status", "1", lul_device)
+
+ luup.call_delay("deusStep", 1, runStamp)
+ debug("deusEnable(): scheduled first step, done")
end
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
-- turn off any controlled lights that are on.
function deusDisable()
+ luup.log("DeusExMachinaII:deusDisable(): disabling, luup.device=" .. tostring(luup.device))
+ luup.variable_set(SWITCH_SID, "Target", "0", luup.device)
+
setMessage("Disabling...")
- local s = getVarNumeric("State", STATE_STANDBY)
- luup.log("DeusExMachinaII:deusDisable(): disabling...")
+
+ local s = getVarNumeric("State", STATE_STANDBY, luup.device)
if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
clearLights()
end
- luup.variable_set(SID, "State", STATE_STANDBY, lul_device)
- luup.variable_set(SID, "Enabled", "0", lul_device)
- luup.variable_set(SWITCH_SID, "Status", "0", lul_device)
+ runStamp = 0
+
+ luup.variable_set(SID, "ScenesRunning", "", luup.device) -- start with a clean slate next time
+ luup.variable_set(SID, "State", STATE_STANDBY, luup.device)
+ luup.variable_set(SID, "Enabled", "0", luup.device)
+ luup.variable_set(SWITCH_SID, "Status", "0", luup.device)
+
setMessage("")
end
-- Initialize.
-function deusInit(deusDevice)
+function deusInit(pdev)
+ luup.log("DeusExMachinaII:deusInit(" .. tostring(pdev) .. "): Version " .. _VERSION .. ", initializing, luup.device=" .. tostring(luup.device))
+
+ runStamp = 0
+
setMessage("Initializing...")
- luup.log("DeusExMachinaII:deusInit(): Version " .. _VERSION .. ", initializing...")
+
+ if debugMode or true then
+ local status, body, httpStatus
+ status, body, httpStatus = luup.inet.wget("http://127.0.0.1:3480/data_request?id=status&DeviceNum=" .. tostring(luup.device) .. "&output_format=json")
+ debug("deusInit(): status %2, startup state is %1", body, status)
+ end
-- One-time stuff
runOnce()
@@ -562,7 +587,7 @@ end
-- delays, so the lights going off looks more "natural" (i.e. not all at once just slamming off).
function deusStep(stepStampCheck)
local stepStamp = tonumber(stepStampCheck)
- luup.log("DeusExMachinaII:deusStep(): wakeup, stamp " .. stepStampCheck)
+ luup.log("DeusExMachinaII:deusStep(): wakeup, stamp " .. tostring(stepStampCheck) .. ", luup.device=" .. tostring(luup.device))
if (stepStamp ~= runStamp) then
luup.log("DeusExMachinaII:deusStep(): stamp mismatch, another thread running. Bye!")
return
@@ -578,7 +603,7 @@ function deusStep(stepStampCheck)
local currentState = getVarNumeric("State", 0)
if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
debug("deusStep(): step in state %1, lightsout=%2, sunset=%3, os.time=%4", currentState,
- luup.variable_get(SID, "LightsOut", lul_device), sunset, os.time())
+ luup.variable_get(SID, "LightsOut", luup.device), sunset, os.time())
debug("deusStep(): luup variables longitude=%1, latitude=%2, timezone=%3, city=%4, sunset=%5, version=%6",
luup.longitude, luup.latitude, luup.timezone, luup.city, luup.sunset(), luup.version)
end
@@ -596,14 +621,14 @@ function deusStep(stepStampCheck)
-- Go to IDLE and delay for next sunset.
luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...")
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
elseif (not isActiveHouseMode()) then
-- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
if (currentState ~= STATE_IDLE) then
luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE, not in an active house mode.")
if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
else
luup.log("DeusExMachinaII:deusStep(): IDLE in an inactive house mode; waiting for mode change.")
end
@@ -618,11 +643,11 @@ function deusStep(stepStampCheck)
setMessage("Waiting for active house mode")
elseif (not inActiveTimePeriod) then
luup.log("DeusExMachinaII:deusStep(): running off cycle")
- luup.variable_set(SID, "State", STATE_SHUTDOWN, lul_device)
+ luup.variable_set(SID, "State", STATE_SHUTDOWN, luup.device)
if (not turnOffLight()) then
-- No more lights to turn off
runFinalScene()
- luup.variable_set(SID, "State", STATE_IDLE, lul_device)
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
luup.log("DeusExMachina:deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
@@ -633,7 +658,7 @@ function deusStep(stepStampCheck)
else
-- Fully active. Find a random target to control and control it.
luup.log("DeusExMachinaII:deusStep(): running toggle cycle")
- luup.variable_set(SID, "State", STATE_CYCLE, lul_device)
+ luup.variable_set(SID, "State", STATE_CYCLE, luup.device)
nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
local devs, max
devs, max = getTargetList()
@@ -676,7 +701,7 @@ function deusStep(stepStampCheck)
if nextCycleDelay ~= nil then
luup.log("DeusExMachinaII:deusStep(): cycle finished, next in " .. nextCycleDelay .. " seconds")
if nextCycleDelay < 1 then nextCycleDelay = 60 end
- luup.call_delay("deusStep", nextCycleDelay, stepStamp, 1)
+ luup.call_delay("deusStep", nextCycleDelay, stepStamp)
else
luup.log("DeusExMachinaII:deusStep(): nil nextCycleDelay, next cycle not scheduled!")
end
From 017a51ebfaa9b3f1bbc7e4801fd53316853145c5 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Mon, 9 Jan 2017 19:00:24 -0500
Subject: [PATCH 41/49] Remove scene target from device list if on or off scene
no longer exists; little code cleanup/remove redundant parameters from
function calls
---
L_DeusExMachinaII1.lua | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index bce2a8b..99d87ef 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -163,7 +163,7 @@ end
-- if the current time is between sunset and off; otherwise 1. Note that all times are reduced
-- to minutes-since-midnight units.
local function isBedtime()
- local testing = getVarNumeric("TestMode", 0, luup.device)
+ local testing = getVarNumeric("TestMode", 0)
if (testing ~= 0) then
luup.log('DeusExMachinaII:isBedtime(): TestMode is on')
debugMode = true
@@ -326,6 +326,7 @@ local function targetControl(targetid, turnOn)
if luup.scenes[onScene] == nil or luup.scenes[offScene] == nil then
-- Both on scene and off scene must exist (defensive).
luup.log("DeusExMachinaII:targetControl(): one or both of the scenes in " .. tostring(targetid) .. " not found in luup.scenes!")
+ removeTarget(targetid)
return
end
debug("targetControl(): on scene is %1, off scene is %2", onScene, offScene)
@@ -538,7 +539,7 @@ function deusDisable()
setMessage("Disabling...")
- local s = getVarNumeric("State", STATE_STANDBY, luup.device)
+ local s = getVarNumeric("State", STATE_STANDBY)
if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
clearLights()
end
From 50d3a3eb968d2966e27d39c0d1800652bc7cb4ac Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Mon, 9 Jan 2017 19:17:14 -0500
Subject: [PATCH 42/49] Don't allow DEMII to operate on itself
---
J_DeusExMachinaII1_UI7.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/J_DeusExMachinaII1_UI7.js b/J_DeusExMachinaII1_UI7.js
index 0961daf..ae5af2c 100644
--- a/J_DeusExMachinaII1_UI7.js
+++ b/J_DeusExMachinaII1_UI7.js
@@ -26,8 +26,10 @@ var DeusExMachinaII = (function(api) {
}
function isControllable(devid) {
+ var v = api.getDeviceState( devid, "urn:toggledbits-com:serviceId:DeusExMachinaII1", "LightsOut" );
+ if (!(v === undefined || v === false)) return false;
if (isDimmer(devid)) return true; /* a dimmer is a light */
- var v = api.getDeviceState( devid, "urn:upnp-org:serviceId:SwitchPower1", "Status" );
+ v = api.getDeviceState( devid, "urn:upnp-org:serviceId:SwitchPower1", "Status" );
if (v === undefined || v === false) return false;
return true;
}
From ddf5dc9d155821f3dc992c6dfafd7c64f55d456b Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 10 Jan 2017 20:10:36 -0500
Subject: [PATCH 43/49] Add example Lua for final scene to detect difference
between run when disabling vs run when going to sleep/idle
---
README.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/README.md b/README.md
index 6f2fc19..394525e 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,19 @@ set this value to some reasonable limit.
DEMII allows a "final scene" to run when DEMII is disabled or turns off the last light after the "lights out" time. This could be used for any purpose. I personally use it to make sure a whole-house off is run, but you could use it to ensure your alarm system is armed, or your garage door is closed, etc.
+The scene can differentiate between DEMII being disabled and DEMII just going to sleep by checking the `Target` variable in service `urn:upnp-org:serviceId:SwitchPower1`. If the value is "0", then DEMII is being disabled. Otherwise, DEMII is going to sleep. The following code snippet, added as scene Lua, will allow the scene to only run when DEMII is being disabled:
+
+```
+local val = luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Target", pluginDeviceId)
+if val == "0" then
+ -- Disabling, so return true (scene execution continues).
+ return true
+else
+ -- Not disabling, just going to sleep. Returning false stops scene execution.
+ return false
+end
+```
+
### Control of DEMII by Scenes and Lua ###
DeusExMachina can be enabled or disabled like a light switch in scenes or through the regular graphical interface (no Lua required),
From c48ddec0e06fe2402967021e826c76d8e0cd9229 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Tue, 10 Jan 2017 20:12:00 -0500
Subject: [PATCH 44/49] Move destruction of runStamp so delay thread won't
execute while we're trying to shut down
---
L_DeusExMachinaII1.lua | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index 99d87ef..ad3e8f6 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -538,12 +538,14 @@ function deusDisable()
luup.variable_set(SWITCH_SID, "Target", "0", luup.device)
setMessage("Disabling...")
+
+ -- Destroy runStamp so any thread running while we shut down just exits.
+ runStamp = 0
local s = getVarNumeric("State", STATE_STANDBY)
if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
clearLights()
end
- runStamp = 0
luup.variable_set(SID, "ScenesRunning", "", luup.device) -- start with a clean slate next time
luup.variable_set(SID, "State", STATE_STANDBY, luup.device)
From db7880181d90d1fd3e58502253485ab72f79b441 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Fri, 13 Jan 2017 13:32:49 -0500
Subject: [PATCH 45/49] Trying to avoid referncing version in feature
descriptions
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 394525e..8cf3495 100644
--- a/README.md
+++ b/README.md
@@ -84,8 +84,8 @@ Both scenes and individual devices (from the device list above) can be used simu
#### Maximum "On" Targets ####
-Version 2.4 also adds the ability to limit the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
-If this limit is 0, there is no limit enforced. If you have DEMII control a large number of devices, it's probably not a bad idea to
+This value sets the limit on the number of targets (devices or scenes) that DEMII can have "on" simultaneously.
+If 0, there is no limit. If you have DEMII controlling a large number of devices, it's probably not a bad idea to
set this value to some reasonable limit.
#### Final Scene ####
From fda9c5c3a798c0559ebaeecceb6094b4761c3eeb Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 15 Jan 2017 07:12:38 -0500
Subject: [PATCH 46/49] Prep for release
---
D_DeusExMachinaII1_UI7.json | 2 +-
L_DeusExMachinaII1.lua | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/D_DeusExMachinaII1_UI7.json b/D_DeusExMachinaII1_UI7.json
index ce95038..3792828 100644
--- a/D_DeusExMachinaII1_UI7.json
+++ b/D_DeusExMachinaII1_UI7.json
@@ -116,7 +116,7 @@
"left":"0",
"Label":{
"lang_tag":"dem_about",
- "text":"DeusExMachina II ver 2.4 2017-01-07 For documentation or to report bugs, please go to the DeusExMachina Github repository . This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation."
+ "text":"DeusExMachina II ver 2.4 2017-01-15 For documentation or to report bugs, please go to the DeusExMachina Github repository . This plugin is offered for use as-is and without warranties of any kind. By using this plugin, you agree to assume all risks in connection with its use without limitation."
},
"Display":{
"Top":"80",
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index ad3e8f6..bf888d4 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -1,6 +1,6 @@
module("L_DeusExMachinaII1", package.seeall)
-local _VERSION = "2.4RC5"
+local _VERSION = "2.4"
local DEMVERSION = 20400
local SID = "urn:toggledbits-com:serviceId:DeusExMachinaII1"
From e6f6393103c9cf798217df7695ec527bf72ba433 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 15 Jan 2017 07:17:20 -0500
Subject: [PATCH 47/49] Prep for release
---
L_DeusExMachinaII1.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index bf888d4..f21b8f0 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -16,7 +16,7 @@ local STATE_CYCLE = 2
local STATE_SHUTDOWN = 3
local runStamp = 0
-local debugMode = true
+local debugMode = false
local function debug(...)
if debugMode then
From 03359ae371cca389f462f3f12f0be4e4863f40b5 Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 15 Jan 2017 09:08:16 -0500
Subject: [PATCH 48/49] One more tweak to make sure Luup restarts don't bug us.
---
L_DeusExMachinaII1.lua | 78 +++++++++++++++++++++++++-----------------
1 file changed, 46 insertions(+), 32 deletions(-)
diff --git a/L_DeusExMachinaII1.lua b/L_DeusExMachinaII1.lua
index f21b8f0..66c7200 100644
--- a/L_DeusExMachinaII1.lua
+++ b/L_DeusExMachinaII1.lua
@@ -91,7 +91,7 @@ end
-- Shortcut function to return state of SwitchPower1 Status variable
local function isEnabled()
- local s = luup.variable_get(SWITCH_SID, "Status", luup.device)
+ local s = luup.variable_get(SWITCH_SID, "Target", luup.device)
if (s == nil or s == "") then return false end
return (s ~= "0")
end
@@ -500,8 +500,8 @@ local function runOnce()
luup.log("DeusExMachinaII:runOnce(): updating config, version " .. tostring(s) .. " < 20400")
luup.variable_set(SID, "MaxTargetsOn", 0, luup.device)
local e = getVarNumeric("Enabled", 0)
- luup.variable_set(SWITCH_SID, "Status", e, luup.device)
luup.variable_set(SWITCH_SID, "Target", e, luup.device)
+ luup.variable_set(SWITCH_SID, "Status", 0, luup.device)
end
-- Update version state last.
@@ -515,6 +515,16 @@ function getVersion()
return _VERSION, DEMVERSION
end
+-- Start stepper running. Note that we don't change state here. The intention is that Deus continues
+-- in its saved operating state.
+local function deusStart()
+ -- Immediately set a new timestamp
+ runStamp = os.time()
+
+ luup.call_delay("deusStep", 1, runStamp)
+ debug("deusStart(): scheduled first step, done")
+end
+
-- Enable DEM by setting a new cycle stamp and scheduling our first cycle step.
function deusEnable()
luup.log("DeusExMachinaII:deusEnable(): enabling, luup.device=" .. tostring(luup.device))
@@ -522,13 +532,15 @@ function deusEnable()
setMessage("Enabling...")
- luup.variable_set(SWITCH_SID, "Status", "1", luup.device)
+ -- Old Enabled variable follows SwitchPower1's Target
luup.variable_set(SID, "Enabled", "1", luup.device)
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
- runStamp = os.time()
+ -- Launch the stepper. It will figure everything else out.
+ deusStart()
- luup.call_delay("deusStep", 1, runStamp)
- debug("deusEnable(): scheduled first step, done")
+ -- SwitchPower1 status now on, we are running.
+ luup.variable_set(SWITCH_SID, "Status", "1", luup.device)
end
-- Disable DEM and go to standby state. If we are currently cycling (as opposed to idle/waiting for sunset),
@@ -539,9 +551,10 @@ function deusDisable()
setMessage("Disabling...")
- -- Destroy runStamp so any thread running while we shut down just exits.
+ -- Destroy runStamp so any running stepper exits when triggered (delay expires).
runStamp = 0
+ -- If current state is cycling or shutting down, rush that function (turn everything off)
local s = getVarNumeric("State", STATE_STANDBY)
if ( s == STATE_CYCLE or s == STATE_SHUTDOWN ) then
clearLights()
@@ -576,11 +589,7 @@ function deusInit(pdev)
checkVersion()
-- Start up if we're enabled
- if (isEnabled()) then
- deusEnable()
- else
- deusDisable()
- end
+ deusStart()
end
-- Run a cycle. If we're in "bedtime" (i.e. not between our cycle period between sunset and stop),
@@ -595,15 +604,17 @@ function deusStep(stepStampCheck)
luup.log("DeusExMachinaII:deusStep(): stamp mismatch, another thread running. Bye!")
return
end
- if (not isEnabled()) then
+ if not isEnabled() then
+ -- Not enabled, so force standby and stop what we're doing.
luup.log("DeusExMachinaII:deusStep(): not enabled, no more work for this thread...")
+ luup.variable_set(SID, "State", STATE_STANDBY, luup.device)
return
end
-- Get next sunset as seconds since midnight (approx)
local sunset = getSunset()
- local currentState = getVarNumeric("State", 0)
+ local currentState = getVarNumeric("State", STATE_STANDBY)
if (currentState == STATE_STANDBY or currentState == STATE_IDLE) then
debug("deusStep(): step in state %1, lightsout=%2, sunset=%3, os.time=%4", currentState,
luup.variable_get(SID, "LightsOut", luup.device), sunset, os.time())
@@ -619,33 +630,36 @@ function deusStep(stepStampCheck)
-- Get going...
local nextCycleDelay = 300 -- a default value to keep us out of hot water
- if (currentState == STATE_STANDBY and not inActiveTimePeriod) then
- -- Transition from STATE_STANDBY (i.e. we're enabling) in the inactive period.
- -- Go to IDLE and delay for next sunset.
- luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE from STANDBY, waiting for next sunset...")
- nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
- luup.variable_set(SID, "State", STATE_IDLE, luup.device)
- setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
- elseif (not isActiveHouseMode()) then
+ if currentState == STATE_IDLE and not inActiveTimePeriod then
+ -- IDLE and not in active time period, probably a Luup restart. Wait for active period.
+ luup.log("DeusExMachinaII:deusStep(): IDLE and waiting for next sunset...")
+ nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
+ setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
+ elseif not isActiveHouseMode() then
-- Not in an active house mode. If we're not STANDBY or IDLE, turn everything back off and go to IDLE.
- if (currentState ~= STATE_IDLE) then
- luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE, not in an active house mode.")
- if (currentState ~= STATE_STANDBY) then clearLights() end -- turn off lights quickly unless transitioning from STANDBY
- luup.variable_set(SID, "State", STATE_IDLE, luup.device)
+ if currentState == STATE_IDLE then
+ luup.log("DeusExMachinaII:deusStep(): IDLE in active period but inactive house mode; waiting for mode change.")
else
- luup.log("DeusExMachinaII:deusStep(): IDLE in an inactive house mode; waiting for mode change.")
+ luup.log("DeusExMachinaII:deusStep(): transitioning to IDLE, not in an active house mode.")
+ if currentState ~= STATE_STANDBY then
+ clearLights()
+ luup.variable_set(SID, "State", STATE_IDLE, luup.device)
+ end
end
-- Figure out how long to delay. If we're lights-out, delay to next sunset. Otherwise, short delay
-- to re-check house mode, which could change at any time, so we must deal with it.
if (inActiveTimePeriod) then
nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ setMessage("Waiting for active house mode")
else
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
+ setMessage("Idle until " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
end
- setMessage("Waiting for active house mode")
- elseif (not inActiveTimePeriod) then
- luup.log("DeusExMachinaII:deusStep(): running off cycle")
+ elseif not inActiveTimePeriod then
+ -- Not in active period, but in active house mode and running state; shut things off...
+ luup.log("DeusExMachinaII:deusStep(): running off cycle, state=" .. tostring(currentState))
luup.variable_set(SID, "State", STATE_SHUTDOWN, luup.device)
if (not turnOffLight()) then
-- No more lights to turn off
@@ -653,14 +667,14 @@ function deusStep(stepStampCheck)
luup.variable_set(SID, "State", STATE_IDLE, luup.device)
nextCycleDelay = sunset - os.time() + getRandomDelay("MinCycleDelay", "MaxCycleDelay")
luup.log("DeusExMachina:deusStep(): all lights out; now IDLE, setting delay to restart cycling at next sunset")
- setMessage("Waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
+ setMessage("Lights-out complete; waiting for sunset " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
else
nextCycleDelay = getRandomDelay("MinOffDelay", "MaxOffDelay", 60, 300)
setMessage("Shut-off cycle, next " .. timeToString(os.date("*t", os.time() + nextCycleDelay)))
end
else
-- Fully active. Find a random target to control and control it.
- luup.log("DeusExMachinaII:deusStep(): running toggle cycle")
+ luup.log("DeusExMachinaII:deusStep(): running toggle cycle, state=" .. tostring(currentState))
luup.variable_set(SID, "State", STATE_CYCLE, luup.device)
nextCycleDelay = getRandomDelay("MinCycleDelay", "MaxCycleDelay")
local devs, max
From 46d198f34ec7403a003f0bc590154a24bb97962a Mon Sep 17 00:00:00 2001
From: Patrick Rigney
Date: Sun, 15 Jan 2017 09:08:50 -0500
Subject: [PATCH 49/49] getVersion() now returns two values, make sure we only
pass the one we care about
---
I_DeusExMachinaII1.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/I_DeusExMachinaII1.xml b/I_DeusExMachinaII1.xml
index 235d189..4f4eda4 100644
--- a/I_DeusExMachinaII1.xml
+++ b/I_DeusExMachinaII1.xml
@@ -62,7 +62,8 @@
-- Ugly hack. Luup seems to only be able to return values from related state variables (see S_), so use a temp
-- one to store the result we want to pass back. Blech. C'mon guys. Amateur hour. Add an extra return argument
-- for a table of return values or something, please?
- luup.variable_set("urn:toggledbits-com:serviceId:DeusExMachinaII1", "TempStorage", demII.getVersion(), lul_device)
+ local vs, vn = demII.getVersion()
+ luup.variable_set("urn:toggledbits-com:serviceId:DeusExMachinaII1", "TempStorage", vs, lul_device)
return true