The GroundworkFirst, lets get the basics out of the way. We of course need [Rainmeter], [Metadata], and [Variables] sections.
Author=Flying Hyrax | flyinghyrax.deviantart.com
MouseOverAction=!Execute [!ShowMeterGroup buttons][!Redraw]
MouseLeaveAction=!Execute [!HideMeterGroup buttons][!Redraw]
Name="Do I Need a Jacket?"
License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0
Information=Tells you if you'll need a jacket, based on current weather information from Yahoo! Weather and threshold values which you can set. | ALL credit for idea due to www.doineedajacket.com | Variables/options consolidated in Settings.txt in skin's root folder.
You can ignore the MouseOver/LeaveAction bits in the [Rainmeter] section for now - that's not important until later.
In order to make it simple for users to edit the options for our skins, and also to make things easy on ourselves if we decide to do more than one version of the skin, we'll put the necessary variables in a separate file using include:
This directs the skin to import the contents of a file called "settings.txt" that is in the same folder as the skin itself. For reference, here are the contents of that file:
font1=Segoe UI Light
Notice the repeat of the [Variables] section header. This is necessary because when include imports the contents of an external file, those contents are read as if they are the last thing in the skin. If we left out the [Variables] header in settings.txt, then Rainmeter would think our variables were part of the last measure or meter in our skin.
WebParser MeasuresNow, lets get on to the meat of the matter, as it were. We'll need some WebParser measures to collect the weather data we'll be using from Yahoo! Weather. I'm not going to explain WebParser and Regular Expressions in this walkthrough, but there are already numerous helpful resources on that. For instance, this WebParser primer. Anyway:
RegExp="(?siU)<yweather:location city="(.*)".*region="(.*)".*country="(.*)".*<yweather:units temperature="(.*)".*speed="(.*)".*speed="(.*)".*<pubDate>(.*)</pubDate>.*<yweather:condition text="(.*)".*temp="(.*)".*low="(.*)""
ErrorString="ixnay on the eatherway"
Calc MeasuresNow we get to the fun stuff. First, we're going to use a CALC measure to compare the current temperature to the values in "settings.txt". We'll make four basic ranges that the temperature can be in: Hot, warm, cold, and freezing. The four ranges are separated by the "hotTemp", "coldTemp", and "freezeTemp" variables.
Formula=#freezeTemp# > mTemp ? 3 : (#coldTemp# > mTemp ? 2 : (#hotTemp# > mTemp ? 1 : 0))
Here's what we're doing with that formula. There are three nested If/Then statements there (see here, scroll down to "Conditional Operator"). To paraphrase: "Is mTemp (the current temperature) less then #freezeTemp#? If yes, then return a value of 3. If not, then is it less than #coldTemp#? If yes..."
You get the idea. Since we're starting at the lowest temperature threshold, we don't have to use both a greater than and a less than comparison to figure out which range the temperature is in. If it's colder than #freezeTemp#, then it's freezing, and if it's not colder than #freezeTemp# but is colder than #coldTemp#, then it has to be in the "cold" range, and so on.
Let's move on. This skin is going to be text based. We need a way to show a little text description of just how cold or warm it is, for humor's sake. We'll use another Calc measure, with some Substitutes:
Substitute="3":"darn cold out","2":"chilly out","1":"temperate","0":"positively balmy"
If we only needed this text, than we could just put the "Substitute=..." back inside [mCalcTemp]. However, we need both - we'll use the raw numbers (0-3) later on, so we need a second measure.
Now we're going to do basically the same thing, only comparing the current wind speed to the wind thresholds:
Formula=#windLow# > mWind ? 0 : (#windHigh# > mWind ? 1 : (#windExtreme# > mWind ? 2 : (#windExtreme# <= mWind ? 3 : 4)))
Substitute="0":"not windy","1":"somewhat breezy","2":"very windy","3":"your computer just blew away"
The main difference is that [mCalcWind] has one more nested conditional operator than [mCalcTemp], and the numbers go the other way - zero first progressing to 3 instead of 3 decreasing to zero. It could be done in either order, but it makes things easier to keep track of later when we actually determine whether or not you need a jacket. Before we do that, we need one more bunch of conditional operators.
In English, the conjunction used between two phrases depends on the agreement or disagreement of the phrases (English majors everywhere will cringe when they read that description, but that's basically it.) In this case, we have two phrases - one for the temperature and one for the wind. Depending on conditions, there are several possibilities:
1) It's cold and windy
2) It's cold, but not windy
3) It's warm, but windy
4) It's warm and not windy.
Now we just need to express that in a formula:
Formula=(mCalcTemp > 1) && (mCalcWind > 0) ? 1 : ((mCalcTemp > 1) && (mCalcWind <= 0) ? 2 : ((mCalcTemp <= 1) && (mCalcWind > 0) ? 2 : ((mCalcTemp <= 1) && (mCalcWind <= 0) ? 1 : 0)))
Notice the logical AND operator (&&), just don't confuse it with the English language and I was talking about above. If the temperature range is greater than 1 (cold or freezing), AND the wind range is greater than 0 (breezy, windy, or a freaking hurricane), then you need a jacket because of both the temperature and the wind. If that's not the case, then we use our nested conditional operators to move on to the next case. Each case gets either a 1 (substituted for "and") or a 2 (substituted for "but").
Lastly, we're finally going generate an actual yes or no answer to our original question: do you need a jacket?
Formula=(1 < mCalcTemp) || (0 < mCalcWind) ? 1 : 0
This one is simple; the "||" is a logical OR operator. If the temperature is in the cold or freezing ranges, OR if the wind is above the "0" range (OR both), then the conditional operator evaluates to true (1), if not, it evaluates to false (0).
Meters...then we use a substitute so that we can plug this info into our main string meter:
Text="You%1 need a jacket."
The "%1" in the "Text=" line is the output of the [mCalcYN] measure from above, which because of the substitute will be either nothing or the word " don't". So the string will read either "You need a jacket" or "You don't need a jacket."
The string meter for the secondary text is bit of a beast to look at, but it's pretty simple if you break it down:
Text="It's %1, %3 %2."
ToolTipText="%9 %10 %11#CRLF#%12#CRLF#%4, %5 %6#CRLF#Wind: %7 %8"
The first three measures are used in "Text=" and come from our the temperature range substitution, wind range substitution, and the measure we used to find the right conjunction depending on the weather conditions. Measures 4 through 12 are all for the "ToolTipText=". This way, the user can mouse over the string and see the actual current weather data, the location that Yahoo! weather associates with the weather location code in "settings.txt," and a timestamp of the last time the weather feed data was updated.
Lastly, we're going to make three little buttons that show up when you mouse over the skin. These image meters will open "settings.txt," refresh the skin, and open the readme file:
This was what those Mouse Actions in the [Rainmeter] section were for. You can see that all three image meters are part of the "buttons" group and are hidden by default; but because of those Mouse Actions they will show whenever the user's mouse goes inside the skin's window.
And that's pretty much all there is to it. As an example skin this might not be the best choice, because the skin needs several other files to work with this code: the settings.txt file, the images for the buttons, etc. However, I though the discussion of the Calc Formulas at least might be helpful to someone, and it was fun and good practice to write this out.