Hi,
I want to create a scene controller for my lights that work both ways. With this I mean that I want to be able to set the scene from OpenHAB, but I also want OpenHAB to know the current scene if the lights are manually set to match a scene. I don’t know if this is the most efficient way to do it, as it is a combination of (trying to) get what I want and learn at the same time. I found some examples on the forum of scene controls, but they were not exactly what I wanted with 2-way functionality and the possibility to set individual items to one value and the other values in a group to other values. Anyway:
I have managed to get the part where I control the scenes from OpenHAB to work properly. However, I cannot get my code for OpenHAB to detect the scene to work reliably. Sometimes it can guess the correct scene, but other times not. It seems to be a bit random.
I have a HashMap that contains my data for all scenes. My house has two floors, and I have one group for the lights per floor. When a member of the groups has changed, I trigger the code to compare the lights that are on with the scene-HashMap to either give a certain reply or a guess (i.e. for the scenes to only control lights on one floor, it can never be certain as the values on the other floors are not checked).
The part that doesn’t work is where I copy the current scene into a new working variable. Some times the data is copied correctly, other times it is not. I guess I need to define the variable in a different way? Does OpenHAB run the loops in parallel on multiple threads and, thus, use the data from a different iteration?
As an example:
I manually set the lights to match the scene “EVENING”. I get the following in the log once my code check’s the scene:
2020-11-09 14:17:39.844 [INFO ] [smarthome.model.script.scene_EVENING] - MScene_Start: {glightsGroundfloor=OFF, glightsFstfloor=OFF}
2020-11-09 14:17:39.849 [INFO ] [smarthome.model.script.scene_EVENING] - Item: vardagsrum_sarfatti
2020-11-09 14:17:39.853 [INFO ] [smarthome.model.script.scene_EVENING] - mscene: {glightsGroundfloor=OFF, glightsFstfloor=OFF}
2020-11-09 14:17:39.857 [INFO ] [smarthome.model.script.scene_EVENING] - Processing item vardagsrum_sarfatti
2020-11-09 14:17:39.865 [INFO ] [eclipse.smarthome.model.script.scene] - scene EVENING fail 1
2020-11-09 14:17:39.874 [INFO ] [eclipse.smarthome.model.script.Scene] - Current scene is not EVENING
If I refresh the rule and trig the code again, it works, and then it stops working again. Looking at the mscene variable printout I can see that the times it works, it for some reason aquires the correct part.
As can be seen, the Mscene does not contain the proper value from start, resulting in a failure to detect the scene. How should I set mscene to make sure I keep the proper value there?
I attach my code below. I have only copied the part to identify a rule (not set it), as that is not relevant here. Please note the code is not cleaned up and finished yet.
EDIT: As the tabs disappear in the codefences, making the code almost unreadable, I will try to summarize the code below:
scenes = newHashMap with two levels that in the first level contain the scene names as keys and a nested Hashmap as values. The nested one contains item names as keys and item values as values.
The problematic code is as follows:
scenes.keySet.forEach[ scene |
var mscene = newHashMap()
mscene = scenes.get(scene)
]
The mscene value does not retrieve the correct data from the scenes-hashmap.
I have tried to add a while loop (as in the code below) to loop until the item count is correct, but it did not help. So I suppose the problem is how I declare the mscene-variable inside the forEach loop.
I have a printout, so that I can see that the scene is correct. So the correct data should be retrieved.
End of edit.
val useFirstPlaussible = false
val scenes = newHashMap(
"EVENING_GOTOSLEEP" -> (newLinkedHashMap(
"vardagsrum_pentiklampaD_control" -> 10,
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"EVENING_TV" -> (newLinkedHashMap(
"vardagsrum_pentiklampaD_control" -> 80,
"vardagsrum_secto" -> 100,
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"EVENING" -> (newLinkedHashMap(
"vardagsrum_pentiklampaD_control" -> 80,
"vardagsrum_secto" -> 100,
"vardagsrum_sarfatti" -> 30,
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"EVENING_OUT" -> (newLinkedHashMap(
"vardagsrum_pentiklampaD_control" -> 80,
"vardagsrum_secto" -> 100,
"vardagsrum_sarfatti" -> 30,
"tambur_taklampa" -> "ON",
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"MORNING" -> (newLinkedHashMap(
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"MORNING_OUT" -> (newLinkedHashMap(
"tambur_taklampa" -> "ON",
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"DINNER" -> (newLinkedHashMap(
"vardagsrum_pentiklampaD_control" -> 80,
"vardagsrum_sarfatti" -> 20,
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"DAY" -> (newLinkedHashMap(
"vardagsrum_secto" -> 100,
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"DAY_OUT" -> (newLinkedHashMap(
"vardagsrum_secto" -> 100,
"tambur_taklampa" -> "ON",
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
)),
"UPSTAIRS_CLEANING" -> (newLinkedHashMap(
"glightsFstfloor" -> "ON"
)),
"DOWNSTAIRS_CLEANING" -> (newLinkedHashMap(
"glightsGroundfloor" -> "ON"
)),
"ALL_OFF" -> (newLinkedHashMap(
"glightsGroundfloor" -> "OFF",
"glightsFstfloor" -> "OFF"
))
)
rule "SceneController" when Item scenecontrol changed or Member of glightsGroundfloor changed or Member of glightsFstfloor changed then
logInfo("SceneGuess", "Start")
// Get state of all items and note those not OFF
var status = newHashMap()
// Iterate through all items and add to status map if not off
glightsGroundfloor.members.forEach[ i |
logInfo("Add", i.toString)
if (i.state == ON || i.state > 0) {
status.put(i.name, i.state)
logInfo("Added", i.toString)
}
]
glightsFstfloor.members.forEach[ i |
if (i.state == ON || i.state > 0) {
status.put(i.name, i.state)
}
]
logInfo("statusgroup", status.toString)
// Figure out scene
var String plaussible = ""
var String certain = ""
// Loop through all scenes
scenes.keySet.forEach [ scene |
// Create copy of scene in memory
var mscene = newHashMap()
while (mscene.size() != scenes.get(scene).size()) {
mscene = scenes.get(scene)
}
logInfo("scene_" + scene.toString, "MScene_Start: " + mscene.toString)
// Loop through current state, and delete the identical states from mscene
var tStatusFailed = false
var tStatusPlaussible = false
status.keySet.forEach [ citem |
if (!tStatusFailed && certain == "") {
logInfo("scene_" + scene.toString, "Item: " + citem.toString)
logInfo("scene_" + scene.toString, "mscene: " + mscene.toString)
// If scene has the item
if (mscene.containsKey(citem)) {
// logInfo("scene_" + scene.toString, "Item found, checking value")
// logInfo("scene_" + scene.toString, "Item " + citem.toString + ": " + mscene.get(citem).toString + " | " + status.get(citem).toString)
// If item has same value
if (mscene.get(citem).toString == status.get(citem).toString) {
// Delete it from the memory map
// logInfo("scene_" + scene.toString, "--Start--")
// logInfo("scene_" + scene.toString, mscene.toString)
mscene.remove(citem)
// logInfo("scene_" + scene.toString, "Removing item " + citem.toString + " from mscene. Result:")
// logInfo("scene_" + scene.toString, mscene.toString)
// logInfo("scene_" + scene.toString, "--End--")
}
}
// Else check if scene has a group command setting different state to the item
else {
logInfo("scene_" + scene.toString, "Processing item " + citem.toString)
// If current item is member of glightsGroundfloor
if (glightsGroundfloor.members.filter[ i | i.name == citem ].size() == 1) {
// If glightsGroundfloor is in the scene
if (mscene.containsKey("glightsGroundfloor")) {
// If value is different than current items state
if (mscene.get("glightsGroundfloor").toString !== status.get(citem).toString) {
// Mark as failed
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 1")
}
// Else OK, as value is same as group value
else {
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 1")
}
}
// Else mark as plaussible, as the value is OK for the scene but not defined by it.
else {
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 2")
}
}
// Else if current item is member of glightsFstfloor
if ((glightsFstfloor.members.filter[ i | i.name == citem].size() == 1) && !tStatusFailed) {
// If glightsFstfloor is in the scene
if (mscene.containsKey("glightsFstfloor")) {
// If value is different than current items state
if (mscene.get("glightsFstfloor").toString !== status.get(citem).toString) {
// Mark as failed
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 2")
}
// Else OK, as value is same as group value
else {
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 3")
}
}
// Else mark as plaussible, as the value is OK for the scene but not defined by it.
else {
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 4")
}
}
}
}
]
// if (!tStatusFailed) {
// logInfo("scene", "scene " + scene.toString + ", not failed")
// }
// if (!tStatusPlaussible) {
// logInfo("scene", "scene " + scene.toString + ", not plaussible")
// }
// Check that the scene contains maximum two values (glightsGroundfloor & glightsFstfloor)
if ((mscene.size() <= 2) && !tStatusFailed) {
// If size is 2 it must contain both groups
if (mscene.size == 2) {
if (mscene.containsKey("glightsGroundfloor") && mscene.containsKey("glightsFstfloor")) {
// If both are OFF or 0
if ((mscene.get("glightsGroundfloor").toString == "OFF" || mscene.get("glightsGroundfloor").toString == "0") && (mscene.get("glightsFstfloor").toString == "OFF" || mscene.get("glightsFstfloor").toString == "0")) {
// OK
} else {
// Mark as failed
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 3")
}
} // Else mark as failed
else {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 4")
}
}
// Else if size is 1, it must be either one
else if (mscene.size == 1) {
// If it contains glightsGroundfloor
if (mscene.containsKey("glightsGroundfloor")) {
// If both are OFF or 0
if (mscene.get("glightsGroundfloor").toString == "OFF" || mscene.get("glightsGroundfloor").toString == "0") {
// Plaussible, but not certain
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 5")
}
// Else failed, as all items are not on
else {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 5")
}
}
// Else if it contains glightsFstfloor
else if (mscene.containsKey("glightsFstfloor")) {
// If both are OFF or 0
if (mscene.get("glightsFstfloor").toString == "OFF" || mscene.get("glightsFstfloor").toString == "0") {
// Plaussible, but not certain
tStatusPlaussible = true
logInfo("scene", "scene " + scene.toString + " plaussible 6")
}
// Else failed, as all items are not on
else {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 6")
}
}
// Else mark as failed, as all items are not on
else {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 7")
}
}
// Else mark as failed, as all items are not on
else {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 8")
}
}
// Else mark as failed, as all items are not on
else if (!tStatusFailed) {
tStatusFailed = true
logInfo("scene", "scene " + scene.toString + " fail 9")
logInfo("scene_" + scene.toString, "scene failed as there are " + mscene.size().toString + " items in mscene:")
logInfo("scene", mscene.toString)
}
// If status failed
if (tStatusFailed) {
logInfo("Scene", "Current scene is not " + scene)
}
// If status plaussible and not failed
if (tStatusPlaussible && !tStatusFailed) {
logInfo("Plaussible " + scene)
if (plaussible == "") {
plaussible = scene
}
}
// If neither failed or plaussible it is certain
if (!tStatusFailed && !tStatusPlaussible) {
certain = scene
}
]
// Decide what scene is set
// If a certain one is set, use that
// if (certain !== "") {
// scenecontrol.postUpdate(certain)
// }
// Else if a plaussible is set, use that
// else if (plaussible !== "") {
// scenecontrol.postUpdate(plaussible)
// }
// Else set unknown
// else {
// scenecontrol.postUpdate("UNKNOWN")
// }
logInfo("Lights_Status", status.toString)
logInfo("Scenes_Plaussible", plaussible.toString)
logInfo("Scenes_Certain", certain.toString)
EDIT: The codefences and tabs does not seem to work properly…?