Summary
The enum object definitions used by the Astro binding for moon phase#name
seem to require conversion toString for logical tests. The value definitions are not imported like ON
or CLOSED
are. I have working rule code, but would really appreciate your thoughts on if there is A Better Way!
Use Case
After upgrading to OpenHAB 2.4.0 with the v2 Astro binding, I wanted to recreate the moon information message spoken at sunset by a previous MisterHouse set of scripts.
The Astro binding has VERY comprehensive calculation abilities, however testing the resulting Items isn’t simple as values such as Moon Phase are Java objects based on enumerated types.
After looking for MoonPhaseNames in the binding documentation, I went looking in the binding source for the values used by the phase#name
channel.
The OH1 binding defines this as public enum MoonPhaseName
in MoonPhaseName.java
The OH2 binding looks to have moved the code internally, with the same definition, just in a different MoonPhaseName.java
location.
Both define a public enum
type with definitions of NEW, WAXING_CRESCENT, FIRST_QUARTER, WAXING_GIBBOUS, FULL, WANING_GIBBOUS, THIRD_QUARTER, WANING_CRESCENT
.
I suspect an internal map is used to convert the enum type toString, but I couldn’t find the code.
Could these enum definitions be used directly like other more common state objects, such as ‘ON’ or ‘CLOSED’, and what’s the best way to use these for comparisons?
Testing
To investigate, I added debug to a test rule using the Astro binding.
astro.items file defines:
String stMoonPhase "Moon Name [%s]" {channel="astro:moon:local:phase#name"}
Number nmMoonAgePc "Moon Age % [%.1f]" {channel="astro:moon:local:phase#agePercent"}
The results below suggest the binding Item object is turned into a text string with the same value as the binding enum.
Test astro.rules code snippets with the corresponding log output
logInfo("AstronomSunset", "stMoonPhase <" + stMoonPhase + ">")
RESULT: stMoonPhase <stMoonPhase (Type=StringItem, State=WANING_GIBBOUS, Label=Moon Name, Category=null)>
logInfo("AstronomSunset", "stMoonPhase.state <" + stMoonPhase.state + ">")
RESULT: stMoonPhase.state <WANING_GIBBOUS>
logInfo("AstronomSunset", "stMoonPhase.state.toString <" + stMoonPhase.state.toString + ">")
RESULT: stMoonPhase.state.toString <WANING_GIBBOUS>
logInfo("AstronomSunset", "nmMoonAgePc <" + nmMoonAgePc + ">")
RESULT: nmMoonAgePc <nmMoonAgePc (Type=NumberItem, State=72.5926333006597, Label=Moon Age %, Category=null)>
logInfo("AstronomSunset", "nmMoonAgePc.state <" + nmMoonAgePc.state + ">")
RESULT: nmMoonAgePc.state <72.5926333006597>
Testing of different comparison code showed direct comparison using enum fails, and I could only get working tests using string conversions. This makes some sense, but doesn’t feel elegant as hard coding ‘magic strings’ in tests might risk failure if the binding changes, or if local language text is used.
// attempt to use the enum defines directly
if (stMoonPhase.state == FULL) {<code>}
RESULT: name 'FULL' cannot be resolved to an item or type;
This suggests the enum type defined in MoonPhaseName.java is not imported (like ON
or CLOSED
are).
I found two comparisons using conversion to String types worked, but would really appreciate your thoughts on a better/ neater method:
if (stMoonPhase.state.toString == "WANING_GIBBOUS") { <code> }
if (stMoonPhase.state.equals("WANING_GIBBOUS")) { <code> }
RESULT: Both comparisons work fine.
Working example rule giving a message at sunset with moon information (uses the above astro.items definitions):
rule "AstronomSunset"
when
Channel 'astro:sun:local:set#event' triggered END
then
logInfo("AstronomSunset", "Rule entry...")
say("Notice, the sun is now setting at " + new DateTimeType().format("%1$tl %1$tM") + ".")
if (stMoonPhase.state.equals("FULL")) {
logInfo("AstronomSunset", "Full Moon")
say("Notice, tonight is a full moon.")
}
if (stMoonPhase.state.equals("NEW")) {
logInfo("AstronomSunset", "New Moon")
say("Notice, tonight is a new moon.")
}
logInfo("AstronomSunset", "Rule exit...")
end
A rather ugly and hacky attempt at another calculation might also work (note the use of explicit float values for the percent moon age):
if ((nmMoonAgePc.state > 49.0) && (nmMoonAgePc.state < 51.0)) {
logInfo("AstronomSunset", "Alternate Full Moon calc")
say("Moon Age Percent test says this is a FULL MOON.")
}
I’d suggest the Astro binding documentation could also benefit from adding the enumerated type text, and example tests such as the above code (that is if there’s not a better way! )
Thanks for reading my detailed query!