Any suggestions for how to limit the maximum change?
The problem is that when the weather condition changes, the color temperature makes a big step and is very noticable. It should ideeally only make amaximum step of approx 100K per change.
See this graph of what happens - The green line (Calculatede temp) should smoothly transition instead of these big steps.
Uuh, that’s hard…
I would recommend an additional Item and rule. The one rule does the change of Value, another rule gets the change, does a comparison between current value and suggested value, if difference is too big, it starts a timer which does the steps to smoothen the transition.
Been trying to get this working.
The first rule is ok, but the second rule which is supposed to apply the value to all lights that are on is not working for me.
Any ideas?
rule "Sun elevation test"
when
Item testSwitch changed
then
logInfo("Light Color Temperature", "Sun elevation test log")
val SunElevation = (AstroSunData_Elevation.state as Number + 6).intValue
val Number Clouds = Localweatherandforecast_Cloudiness.state
val weatherCond = Localweatherandforecast_WeatherCondition.state
val weatherID = Localweatherandforecast_Current_Conditionid.state
val outputstart = 2200
val outputend = 6000
val inputstart = 0
val inputend = 46
val Number output1 = outputstart + (outputend - outputstart) / (inputend - inputstart) * (SunElevation - inputstart)
val Number output2 = Math.min(Math.max(output1, 2200), 6000)
logInfo("Calulated temperature by Sun elevation", output2.toString)
val Number cloudFactor = Math.floor( (output2.intValue - 2200) * (Clouds.intValue * 0.002))
logInfo("cloudFactor", cloudFactor.toString)
val Number newColourValue = Math.min(Math.max((output2.intValue - cloudFactor.intValue), 2200), 6000)
logInfo("newColourValue", newColourValue.toString)
Calculated_ColorTemperature.sendCommand(newColourValue)
end
rule "Updated Calculated Color" // Send Calculated Color Temperature to all lights thata are ON
when
Item Calculated_ColorTemperature changed
then
logInfo("Updated Calculated Color", "Running Color Temp Update")
gAll_Lights_Brightness.members.filter[i|i.state > 0].forEach[item|
gAll_Lights_ColorTemperature.members.filter[j|j.name.contains(item.name)].head.sendCommand(newState)
]
end
rule "Updated Calculated Color" // Send Calculated Color Temperature to all lights thata are ON
when
Item Calculated_ColorTemperature changed
then
logInfo("Updated Calculated Color", "Running Color Temp Update")
gAll_Lights_Brightness.members.filter[i|(i.state as Number) > 0].forEach[item|
gAll_Lights_ColorTemperature.members.filter[j|j.name.contains(item.name)].head.sendCommand(newState)
]
end
Are the Item names matching (i.e. having Items like MyLight in Group gAll_Lights_Brightness and the matiching Item MyLightColortemp in Group gAll_Lights_ColorTemperature)?
Yes. They have the same name except for the last piece.
Is this why it isn’t working? Because it’s trying to match the whole name including the ‘_Brightness’ bit?
That’ pretty self-evident. One of your Items does not yet have a valid state.
That’s always a possibility e.g. a device is unplugged etc. You can code around it by testing before trying to handle it as number.
Your last question was about string manipulation, not filters.
Item with name fred_banana.
Item with name fred_apple.
If you have source Item fred_banana, how do you make “fred_apple”?
val targetSubstring = sourceItem.name.split("_")
// that creates an array with parts "fred" and "banana"
// part 0 and part 1 respectively
val targetItemName = targetSubstring.get(0) + "_apple"
// we can pick one part and just stitch strings together
// now we have a string "fred_apple"
// that's just a string
// we can do limited stuff with an Item name string
sendCommand(targetItemName, "ON")
// but often you'll want to get the actual Item
val directItem = ScriptServiceUtil.getItemRegistry.getItem(targetItemName)
// but this will blow up and stop the rule if that Item does not exist
val searchItem = gSomeGroup.members.findFirst[ j | j.name == targetItemName]
// .findFirst[ ] is just a handy shortcut version of .filter[ ].head
// this will return null if it doesn't exist
// allowing your rule to continue and deal with it gracefully
if (searchItem === null) {
logInfo("test", "Oh dear, Item " + targetItemName + " not found")
}
Thanks. I think this is what was asking for. However, after thinking about this, trying to apply this to my problem doesn’t work. For example, Kitchen_Ceiling_Light_Brightness and Hall_Lamp have a different number of splits, so I can’t use a set number of splits to get the right bit.
Where can I find the refences to these things like .name .split .get(0) .head? I can’t find it in the documentation. I’m wondering if I can somehow get the last split and then replace it with _ColorTemperature.
Sorry for any vagueness. I had logged everything out to get to this point and this is the last piece I’m not sure how to break down and test.
The rule is triggering and I have been monitoring the logs, but no further errors are being shown.
I added a log to this filter at the end so it looks like
logInfo("Updated Calculated Color", "Running Color Temp Update")
gAll_Lights_Brightness.members.filter[i|(i.state as Number) > 0].forEach[item|
gAll_Lights_ColorTemperature.members.filter[j|j.name.contains(item.name)].head.sendCommand(newState)
logInfo("loggerName", item.toString)
]
And in the logs, when the rule triggers, I get a log line for each light that is on which looks like
I believe this is the full item object being listed from the first filter. But I can’t see how I get from this to the second part, changing _Brightness for _ColorTemperature to get the right item name.
newState is the new state of the CalculatedColorTemperature item that triggers the rule
Part of Xtend’s way to process Lists. Their docs are all over the place though. It’s a layer on top of Java’s Stream API. I’ve consolidated this at the following DP.
Like I said, save each step to it’s own variable instead of trying to do everything all on one line.
val members = gAll_Lights_Brightness.members
logInfo("test", "All members: " + members)
val filtered = members.filter[i | i.state as Number > 0]
logInfo("test", "Filtered: " + filtered)
filtered.forEach[item |
logInfo("test", "Processing Item " + item.name)
// here is where it's better to do String manipulation to convert item.name to the ColorTemperature
// Item's name, even if it means you have to rename those Items to fit the pattern
// sendCommand(item.name.replace("Lamp", "ColorTemperature"), newState.toString)
// or maybe it's as simple as
// sendCommand(item.name + "_ColorTemperature", newState.toString)
// As explained in the working with groups DP linked above, use findFirst instead of filter and head
val commandItem = gAll_Lights_ColorTemperature.members.findFirst[ j | j.name.contains(item.name) ]
logInfo("test", "Color Item found is " + commandItem) // if this is null the Item was not found
commandItem.sendCommand(newState)
]
Break it up and log each step of the way. If you do half a dozen operations all on the same line and it doesn’t work it’s impossible to figure out why.
These are features of the Xtend language upon which DSL is based.
In turn that’s based on Java, so Java tutorials will give you insight into what you can do to with a Java string object.
If you choose a more consistent Item naming convention, you make it easier for yourself.
(I expect most of us oldtimers have gone through several renaming/restructuring exercises already).
However;
val randomName = "some_old_rubbish_lamp"
val parts = randomName.split("_")
var n = 0
var newName = ""
while (parts.get(n) != "lamp") {
newName = newName + parts.get(n) + "_"
// remember to put the underscores back
n = n + 1
}
newName = newName + "brightness"
logInfo("test", "new name " + newName)
// logs out string "some_old_rubbish_brightness"
I’ve learnt maybe 1% of what you’ve laid out here so far, but I’ll keep going.
My rule now works and I can go about cleaning it up, refining it, and tweaking it further.
Here are the finished rules:
rule "Sun Color Temperature - Set Color Temperature by Sun Elevation"
when
Item AstroSunData_Elevation changed
then
logInfo("Color Temperature", "Sun elevation has changed, updating Calculated Color Temperture if necessary")
if (newState >= -6) {
val SunElevation = (AstroSunData_Elevation.state as Number + 6).intValue
val Number Clouds = Localweatherandforecast_Cloudiness.state
val weatherCond = Localweatherandforecast_WeatherCondition.state
val weatherID = Localweatherandforecast_Current_Conditionid.state
val outputstart = 2200
val outputend = 6000
val inputstart = 0
val inputend = 46
val Number output1 = outputstart + (outputend - outputstart) / (inputend - inputstart) * (SunElevation - inputstart)
val Number output2 = Math.min(Math.max(output1, 2200), 6000)
val Number cloudFactor = Math.floor( (output2.intValue - 2200) * (Clouds.intValue * 0.002))
val Number newColourValue = Math.min(Math.max((output2.intValue - cloudFactor.intValue), 2200), 6000)
Calculated_ColorTemperature.sendCommand(newColourValue)
logInfo("Color Temperature", "Calculated Temp is " + output2 + "K - Cloud Factor (" + Clouds + ". Calculated as " + cloudFactor + "K) = " + (output2 - cloudFactor) + "K")
}
end
rule "Sun Color Temperature - Send Calculated Color to all lights thata are ON"
when
Item Calculated_ColorTemperature received update
then
logInfo("Color Temperature", "Applying Color Temperature to all lights that are ON")
gAll_Lights_Brightness.members.filter[i|(i.state as Number) > 0].forEach[item|
gAll_Lights_ColorTemperature.members.filter[j|j.name.contains(item.name.replace("_Brightness", "_ColorTemperature"))].head.sendCommand(newState)
]
end
end```