/**
* Weather Panel
*
* Copyright 2023 Sidney Johnson
* If you like this code, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Version: 1.0 - Initial Version
* Version: 1.1 - Fixed font size not changing the font size
* Version: 1.2 - Decoupled weather data refresh from wallpaper refresh
* Version: 1.3 - Minor formating tweaks, removed all static data from json
* Version: 2.0 - Addeded 3 day forcast and more formating and presentation tweaks. Removed weather station requirement
* Version: 2.1 - Preloads images for smoother transitions
* Version: 2.1.1 - Added dynamic API URL
* Version: 2.2 - Added support for user selectable Station ID
* Version: 2.2.1 - Added better browser support
* Version: 2.3 - Upgraded Icons
* Version: 2.4 - TWC Weather Update
* Version: 2.5 - Added Rain Switch
* Version: 2.6 - Hubitat updates
* Version: 2.7 - Hubitat local updates
*
*/
import java.text.SimpleDateFormat
import groovy.time.TimeCategory
definition(
name: "Weather Panel",
namespace: "sidjohn1",
author: "Sidney Johnson",
description: "Weather Panel, a Hubitat web client",
category: "Convenience",
iconUrl: "",
iconX2Url: "",
iconX3Url: "",
importUrl: 'https://raw.githubusercontent.com/sidjohn1/hubitat/main/WeatherPanel/weatherpanel.groovy',
oauth: true)
preferences {
page(name: "selectDevices")
page(name: "viewURL")
}
def selectDevices() {
dynamicPage(name: "selectDevices", install: true, uninstall: true) {
section("About") {
paragraph "Weather Panel displays inside and outside temp and weather infomation as a web page. Also has a random customizable background."
paragraph "${textVersion()}\n${textCopyright()}"
}
section("Select...") {
input "insideTemp", "capability.temperatureMeasurement", title: "Inside Tempature...", multiple: false, required: false
input "outsideTemp", "capability.temperatureMeasurement", title: "Outside Tempature...", multiple: false, required: false
input "forcastDevice", "capability.temperatureMeasurement", title: "Forcast Device...", multiple: false, required: false
input "showForcast", "bool", title:"Show Forcast", required: false, multiple:false
input "stationID", "text", title:"Station ID (Optional)", required: false, multiple:false
input "rainswitch", "capability.switch", title:"Rain Switch (Optional)", required: false, multiple:false
}
section(hideable: true, hidden: true, "Optional Settings") {
input "fontColor", "enum", title:"Select Font Color", required: false, multiple:false, defaultValue: "White", options: [3: 'Black',2: 'Ivory', 1:'White']
input "fontSize", "enum", title:"Select Font Size", required: false, multiple:false, defaultValue: "Medium", options: [4: 'xSmall',3: 'Small',2: 'Medium', 1:'Large']
input "localResources", "bool", title: "Use Local Resources?", required: false, defaultValue: false
}
section("Wallpaper URL") {
input "wallpaperUrl", "text", title: "Wallpaper URL",defaultValue: "http://", required:false
}
section() {
href "viewURL", title: "View URL"
}
}
}
def viewURL() {
dynamicPage(name: "viewURL", title: "${title ?: location.name} Weather Pannel URL", install:false) {
section() {
paragraph "Copy the URL below to any modern browser to view your ${title ?: location.name}s' Weather Panel. Add a shortcut to home screen of your mobile device to run as a native app."
input "weatherUrl", "text", title: "URL",defaultValue: "${generateURL("html")}", required:false
href url:"${generateURL("html")}", style:"embedded", required:false, title:"View", description:"Tap to view, then click \"Done\""
}
}
}
mappings {
path("/html") { action: [GET: "generateHtml"] }
path("/json") { action: [GET: "generateJson"] }
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unschedule()
unsubscribe()
initialize()
}
def initialize() {
log.info "Weather Panel ${textVersion()} ${textCopyright()}"
generateURL()
}
def generateHtml() {
render contentType: "text/html", headers: ["Access-Control-Allow-Origin": "*"], data: "\n\n
${head()}\n\n${body()}\n"
}
def generateJson() {
render contentType: "application/json", headers: ["Access-Control-Allow-Origin": "*"], data: "${jsonData()}"
}
def head() {
def color1
def color2
def font1
def font2
def font3
def iconW
def rTimeout
def temp1TA
def temperatureScale = getTemperatureScale()
def weatherDataContent
rTimeout = Math.floor(Math.random() * (1000000 - 800000 + 1) ) + 1750000
rTimeout = rTimeout.toInteger()
switch (settings.fontSize) {
case "1":
font1 = "50"
font2 = "20"
font3 = "10"
break;
case "2":
font1 = "48"
font2 = "18"
font3 = "10"
break;
case "3":
font1 = "46"
font2 = "16"
font3 = "10"
break;
case "4":
font1 = "44"
font2 = "16"
font3 = "7"
break;
}
switch (settings.fontColor) {
case "1":
color1 = "255,255,255"
color2 = "0,0,0"
color3 = "0,0,0"
break;
case "2":
color1 = "255,248,220"
color2 = "222,184,135"
color3 = "0,0,0"
break;
case "3":
color1 = "0,0,0"
color2 = "255,255,255"
color3 = "255,255,255"
break;
}
if (settings.localResources) {
fontURL = "/local/"
}
else {
fontURL = "https://sidjohn1.github.io/hubitat/weatherpanel/"
}
if (showForcast == true) {
iconW = "47"
temp1TA = "right"
weatherDataContent = """ content += '
';
content += '' + item.temp1 + '°${temperatureScale}
Inside
' + item.temp2 + '°${temperatureScale}
Outside
';
content += '';
content += '' + item.forecastDay + '
' + item.forecastDayHigh + '
' + item.forecastDayLow + '
';
content += '' + item.forecastDay1 + '
' + item.forecastDayHigh1 + '
' + item.forecastDayLow1 + '
';
content += '' + item.forecastDay2 + '
' + item.forecastDayHigh2 + '
' + item.forecastDayLow2 + '
';
content += '' + item.forecastDay3 + '
' + item.forecastDayHigh3 + '
' + item.forecastDayLow3 + '
';"""
}
else {
iconW = "100"
temp1TA = "left"
weatherDataContent = """ content += '
';
content += '' + item.temp1 + '°${temperatureScale}
Inside
';
content += '' + item.temp2 + '°${temperatureScale}
Outside
';
content += '';"""
}
"""
Weather Panel
"""
}
def body() {
""""""
}
def jsonData(){
//log.debug "refreshing weather"
sendEvent(linkText:app.label, name:"weatherRefresh", value:"refreshing weather", descriptionText:"weatherRefresh is refreshing weather", eventType:"SOLUTION_EVENT", displayed: true)
def alerts
def astronomy
def current
def currentTemp
def forecast
int forecastDayHigh
def forecastDayHigh1
def forecastDayHigh2
def forecastDayHigh3
int forecastDayLow
def forecastDayLow1
def forecastDayLow2
def forecastDayLow3
def temperatureScale = getTemperatureScale()
def forecastDay1
def forecastNight1
def forecastDay2
def forecastNight2
def forecastDay3
def forecastNight3
def forecastDay4
def forecastNight4
forecastDayHigh = forcastDevice.currentValue('todaysHigh').toInteger()
forecastDayLow = forcastDevice.currentValue('todaysLow').toInteger()
def weatherIcons = []
if (forecastDayLow < forecastDayHigh){
// log.debug "$forecastDayLow $forecastDayHigh true"
forecastDay1 = forcastDevice.currentValue("zforecast_1").split(':')
forecastNight1 = forcastDevice.currentValue("zforecast_2").split(':')
forecastDay2 = forcastDevice.currentValue("zforecast_3").split(':')
forecastNight2 = forcastDevice.currentValue("zforecast_4").split(':')
forecastDay3 = forcastDevice.currentValue("zforecast_5").split(':')
forecastNight3 = forcastDevice.currentValue("zforecast_6").split(':')
forecastDay4 = forcastDevice.currentValue("zforecast_7").split(':')
forecastNight4 = forcastDevice.currentValue("zforecast_8").split(':')
}
else {
// log.debug "$forecastDayLow $forecastDayHigh false"
forecastDay1 = forcastDevice.currentValue("zforecast_2").split(':')
forecastNight1 = forcastDevice.currentValue("zforecast_3").split(':')
forecastDay2 = forcastDevice.currentValue("zforecast_4").split(':')
forecastNight2 = forcastDevice.currentValue("zforecast_5").split(':')
forecastDay3 = forcastDevice.currentValue("zforecast_6").split(':')
forecastNight3 = forcastDevice.currentValue("zforecast_7").split(':')
forecastDay4 = forcastDevice.currentValue("zforecast_8").split(':')
forecastNight4 = forcastDevice.currentValue("zforecast_9").split(':')
}
if (forcastDevice.currentValue("icon").contains(/day/)){
weatherIcons = ["skc": "day-sunny", "few": "day-sunny", "sct": "day-cloudy", "bkn": "day-cloudy", "ovc": "day-cloudy", "wind_skc": "day-windy", "wind_few": "day-windy", "wind_sct": "day-windy", "wind_bkn": "day-windy", "wind_ovc": "day-windy", "snow": "day-snow", "rain_snow": "day-rain-mix", "rain_sleet": "day-sleet", "snow_sleet": "day-sleet", "fzra": "day-sleet", "rain_fzra": "day-sleet", "snow_fzra": "day-sleet", "sleet": "day-sleet", "rain": "day-showers", "rain_showers": "day-showers", "rain_showers_hi": "day-showers", "tsra": "day-storm-showers", "tsra_sct": "day-storm-showers", "tsra_hi": "day-storm-showers", "tornado": "tornado", "hurricane": "hurricane", "tropical_storm": "hurricane", "dust": "day-haze", "smoke": "day-haze", "haze": "day-haze", "hot": "hot", "cold": "snowflake-cold", "blizzard": "snowflake-cold", "fog": "day-haze"]
}
else if (forcastDevice.currentValue("icon").contains(/night/)){
weatherIcons = ["skc": "night-clear", "few": "night-clear", "sct": "night-alt-cloudy", "bkn": "night-alt-cloudy", "ovc": "night-alt-cloudy", "wind_skc": "alt-cloudy-windy", "wind_few": "alt-cloudy-windy", "wind_sct": "alt-cloudy-windy", "wind_bkn": "alt-cloudy-windy", "wind_ovc": "alt-cloudy-windy", "snow": "night-alt-snow", "rain_snow": "night-alt-rain-mix", "rain_sleet": "night-alt-sleet", "snow_sleet": "night-alt-sleet", "fzra": "night-alt-sleet", "rain_fzra": "night-alt-sleet", "snow_fzra": "night-alt-sleet", "sleet": "night-alt-sleet", "rain": "night-alt-showers", "rain_showers": "night-alt-showers", "rain_showers_hi": "night-alt-showers", "tsra": "night-alt-storm-showers", "tsra_sct": "night-alt-storm-showers", "tsra_hi": "night-alt-storm-showers", "tornado": "tornado", "hurricane": "hurricane", "tropical_storm": "night-alt-thunderstorm", "dust": "night-fog", "smoke": "night-fog", "haze": "night-fog", "hot": "hot","cold": "snowflake-cold","blizzard": "snowflake-cold","fog": "night-fog"]
}
def forecastNow = forcastDevice.currentValue("icon").split('/')
forecastNow = forecastNow[4].split('\\?')
forecastNow = forecastNow[0].split('\\,')
forecastNow = weatherIcons[forecastNow[0].toString()]
def forecastDay1Icon = forecastDay1[7].split('/')
forecastDay1Icon = forecastDay1Icon[4].split('\\?')
forecastDay1Icon = forecastDay1Icon[0].split('\\,')
forecastDay1Icon = weatherIcons[forecastDay1Icon[0].toString()]
def forecastDay2Icon = forecastDay2[7].split('/')
forecastDay2Icon = forecastDay2Icon[4].split('\\?')
forecastDay2Icon = forecastDay2Icon[0].split('\\,')
forecastDay2Icon = weatherIcons[forecastDay2Icon[0].toString()]
def forecastDay3Icon = forecastDay3[7].split('/')
forecastDay3Icon = forecastDay3Icon[4].split('\\?')
forecastDay3Icon = forecastDay3Icon[0].split('\\,')
forecastDay3Icon = weatherIcons[forecastDay3Icon[0].toString()]
def forecastDay4Icon = forecastDay4[7].split('/')
forecastDay4Icon = forecastDay4Icon[4].split('\\?')
forecastDay4Icon = forecastDay4Icon[0].split('\\,')
forecastDay4Icon = weatherIcons[forecastDay4Icon[0].toString()]
def today = new Date()
use(TimeCategory) {
date1 = (new Date())+1.day
date2 = (new Date())+2.day
date3 = (new Date())+3.day
sdf = new SimpleDateFormat("E' - 'dd")
forecastDate1 = sdf.format(today)
forecastDate2 = sdf.format(date1)
forecastDate3 = sdf.format(date2)
forecastDate4 = sdf.format(date3)
}
"""{"data": [{"icon":"${forecastNow}","cond":"${forcastDevice.currentValue("textDescription")}","temp1":"${Math.round(insideTemp.currentValue("temperature"))}","temp2":"${Math.round(outsideTemp.currentValue("temperature"))}"
,"forecastDay":"${forecastDate1}","forecastIcon":"${forecastDay1Icon}","forecastDayHigh":"${forecastDay1[2]}","forecastDayLow":"${forecastNight1[2]}"
,"forecastDay1":"${forecastDate2}","forecastIcon1":"${forecastDay2Icon}","forecastDayHigh1":"${forecastDay2[2]}","forecastDayLow1":"${forecastNight2[2]}"
,"forecastDay2":"${forecastDate3}","forecastIcon2":"${forecastDay3Icon}","forecastDayHigh2":"${forecastDay3[2]}","forecastDayLow2":"${forecastNight3[2]}"
,"forecastDay3":"${forecastDate4}","forecastIcon3":"${forecastDay4Icon}","forecastDayHigh3":"${forecastDay4[2]}","forecastDayLow3":"${forecastNight4[2]}"}]}"""
}
private def generateURL(data) {
if (!state?.accessToken) {
try {
def accessToken = createAccessToken()
log.debug "Creating new Access Token: $state.accessToken"
} catch (ex) {
log.error "Enable OAuth in SmartApp IDE settings for Weather Panel"
log.error ex
}
}
def url = "${getFullLocalApiServerUrl()}/${data}?access_token=${state.accessToken}"
return "$url"
}
private def textVersion() {
def text = "Version 2.7"
}
private def textCopyright() {
def text = "Copyright © 2022 Sidjohn1"
}