When pulling observation data from the Tempest how can you determine if it is sunny or cloudy? The starting point seems to be the Illuminance data but that varies so much depending on the time of day that the readings early in the morning on a sunny day might be lower than the readings at noon on a cloudy day.
Any ideas would be welcome.
Thanks in advance for the help
It is difficult to determine what I like to call “SkyState” from the data from your Tempest …
I have an algorithm that uses the UV data, but requires some knowledge about the theoretical max UV for any specific time on any specific day of the year. A good approximation, but not foolproof.
Problem is, UV, solar radiation and illuminance vary greatly on sunny days…imagine those days that you see only a cloud or two. It IS a sunny day, not partly cloudy…but for the time that the cloud passes over you, you would assume it’s cloudy, as those measurements would drop.
Anyway, you can approximate it, but you’ll never get it right 100%.
Stealing this from the SmartThings algorithm from their legacy Groovy weather device handler. Basically, it sets an artificial lux value based on time of day.
private estimateLux(sunriseDate, sunsetDate, weatherIcon) {
def lux = 0
def now = new Date().time
if (now > sunriseDate.time && now < sunsetDate.time) {
//day
switch(weatherIcon) {
case 'tstorms':
lux = 200
break
case ['cloudy', 'fog', 'rain', 'sleet', 'snow', 'flurries',
'chanceflurries', 'chancerain', 'chancesleet',
'chancesnow', 'chancetstorms']:
lux = 1000
break
case 'mostlycloudy':
lux = 2500
break
case ['partlysunny', 'partlycloudy', 'hazy']:
lux = 7500
break
default:
//sunny, clear
lux = 10000
}
//adjust for dusk/dawn
def afterSunrise = now - sunriseDate.time
def beforeSunset = sunsetDate.time - now
def oneHour = 1000 * 60 * 60
if(afterSunrise < oneHour) {
//dawn
lux = (long)(lux * (afterSunrise/oneHour))
} else if (beforeSunset < oneHour) {
//dusk
lux = (long)(lux * (beforeSunset/oneHour))
}
}
else {
//night - always set to 10 for now
//could do calculations for dusk/dawn too
lux = 10
}
lux
}
Reverse engineering it:
If within 1 hour after sunrise or 1 hour before sunset, get a percentage value of now to sunrise/sunset and multiply it by the measured lux to get an adjusted lux value
then:
if lux > 10000 = sunny
if lux > 7500 = partly sunny
if lux > 2500 = mostly cloudy
if lux > 1000 = cloudy
if lux > 10 = storms
else = night
The method I use is to look at my graphs and the yellow sinusoidal curve showing the sun gets dips where clouds shade the sun.
https://home.exetel.com.au/flyfree/window/TOBgraphs.html
Cheers Ian
Thanks @btst.online and @mabeatty1978
That algorithm looks like a very reasonable approximation.
Thanks again.
Thanks @iladyman that is an interesting approach
I calculate Solar Isolation (solar radiation with no clouds); I compare that to actual solar radiation. If measured solar radiation is greater than predicted that is usually an indication of a partly cloudy condition where the clouds are not currently shading the sensor and the existing clouds are reflecting more light back to the sensor.
Partly Cloudy and Cloudy determination:
if solar_el <= 0: # Can not determine clouds at night
cloudy = False
part_cloud = False
else:
si_p = round(((solar_rad) / (solar_ins))) * 100
si_d = round((solar_ins) - (solar_rad))
if ((si_p <= 50) and (si_d >= 50)):
cloudy = True
part_cloud = False
elif ((si_p <= 75) and (abs(si_d) >= 15)):
part_cloud = True
cloudy = False
elif ((si_p >= 115) and (abs(si_d) >= 15)):
part_cloud = True
cloudy = False
else:
part_cloud = False
cloudy = False
Solar Isolation:
def solar_insolation(self, elevation, latitude, longitude):
""" Return Estimation of Solar Radiation at current sun elevation angle.
Input:
Elevation in Meters
Latitude
Longitude
Where:
solar_elevation is the Sun Elevation in Degrees with respect to the Horizon
sz is Solar Zenith in Degrees
ah is (Station Elevation Compensation) Constant ah_a = 0.14, ah_h = Station elevation in km
am is Air Mass of atmoshere between Station and Sun
1353 W/M^2 is considered Solar Radiation at edge of atmoshere
** All Trigonometry Fuctions need Degrees converted to Radians **
"""
if elevation is None or latitude is None or longitude is None:
return None
# Calculate Solar Elevation
solar_elevation = self.solar_elevation(latitude, longitude)
cos = math.cos
sin = math.sin
asin = math.asin
radians = math.radians
degrees = math.degrees
se = solar_elevation
sz = 90 - se
ah_a = 0.14
ah_h = elevation / 1000
ah = ah_a * ah_h
if se >= 0:
am = 1/(cos(radians(sz)) + 0.50572*pow((96.07995 - sz),(-1.6364)))
si = (1353 * ((1-ah)*pow(.7, pow(am, 0.678))+ah))*(sin(radians(se)))
else:
am = 1
si = 0
si = round(si)
return si
The above is in python, just the parts I think you need are included. I do have some modification I have been experimenting with for solar insulation, but the above is fairly accurate. I have my testing code in java-script running in node red. The above is what I put into hass-Weatherflow2mqtt.
This is along those lines, just a little bit more in depth:
def current_conditions(self, lightning_1h, precip_type, rain_rate, wind_speed, solar_el, solar_rad, solar_ins, snow_prob, fog_prob):
""" Return local current conditions based on only weather station sesnors.
Input:
lightning_1h (#)
**** Need to use the precip type number from Tempest so to get it before translations ****
precip_type (#)
rain_rate (imperial or metric)
wind_speed (metric)
solar_el (degrees)
solar_rad (metric)
snow_prob (%)
fog_prob (%)
Where:
lightning_1h is lightning strike count within last hour
precip_type is the type of precipitation: rain / hail
rain_rate is the rain fall rate
wind_speed is the speed of wind
solar_el is the elevation of the sun with respect to horizon
solar_rad is the measured solar radiation
solar_ins is the calculated solar radiation
snow_prob is the probability of snow
fog_prob is the probability of fog
si_p is the percentage difference in Solar Radiation and Solar Insolation
si_d is the numeral difference in Solar Radiation and Solar Insolation
cloudy is Boolan for cloud state
part_cloud is Boolan for partly cloud state
current is the Local Current Weather Condition
"""
if (
lightning_1h is None
or precip_type is None
or rain_rate is None
or wind_speed is None
or solar_el is None
or solar_rad is None
or solar_ins is None
or snow_prob is None
or fog_prob is None
):
_LOGGER.info("Something is missing to calculate current conditions %s - %s - %s - %s - %s - %s - %s - %s - %s", lightning_1h, precip_type, rain_rate, wind_speed,solar_el, solar_rad, solar_ins, snow_prob, fog_prob)
return "clear-night"
# Home Assistant weather conditions: clear-night, cloudy, fog, hail, lightning, lightning-rainy, partlycloudy, pouring, rainy, snowy, snowy-rainy, sunny, windy, windy-variant, exceptional
# Exceptional not used here
if solar_el <= 0: # Can not determine clouds at night
cloudy = False
part_cloud = False
else:
si_p = round(((solar_rad) / (solar_ins))) * 100
si_d = round((solar_ins) - (solar_rad))
if ((si_p <= 50) and (si_d >= 50)):
cloudy = True
part_cloud = False
elif ((si_p <= 75) and (abs(si_d) >= 15)):
part_cloud = True
cloudy = False
elif ((si_p >= 115) and (abs(si_d) >= 15)):
part_cloud = True
cloudy = False
else:
part_cloud = False
cloudy = False
if ((lightning_1h >= 1) and (rain_rate >= 0.01)): # any rain at all
current = "lightning-rainy"
elif (lightning_1h >= 1):
current = "lightning"
elif (precip_type == 2):
current = "hail"
elif (rain_rate >= 7.8): # pouring => Imperial >= 0.31 in/hr, Metric >= 7.8 mm/hr
current = "pouring"
elif ((snow_prob >= 50) and (rain_rate >= 0.01)): # any rain at all
current = "snowy-rainy"
elif (rain_rate >= 0.01): # any rain at all
current = "rainy"
elif ((wind_speed >= 11.17) and (cloudy)): # windy => Imperial >= 25 mph, Metric >= 11.17 m/s
current = "windy-variant"
elif (wind_speed >= 11.17): # windy => Imperial >= 25 mph, Metric >= 11.17 m/s
current = "windy"
elif (fog_prob >= 50):
current = "fog"
elif ((snow_prob >= 50) and (cloudy)):
current = "snowy"
elif (cloudy == 'true'):
current = "cloudy"
elif (part_cloud):
current = "partlycloudy"
elif (solar_el >= 0 ): # if daytime
current = "sunny"
else:
current = "clear-night"
# return the standard weather conditions as used by Home Assistant
return current
''' def current_conditions_txt(self, current_conditions):
# Clear Night, Cloudy, Fog, Hail, Lightning, Lightning & Rain, Partly Cloudy, Pouring Rain, Rain, Snow, Snow & Rain, Sunny, Windy, Wind & Rain, exceptional (not used)
# Need translations
# Add input blurb
if (current_conditions = "lightning-rainy"):
current = "Lightning & Rain"
elif (current_conditions = "lightning"):
current = "Lightning"
elif (current_conditions = "hail"):
current = "Hail"
elif (current_conditions = "pouring"):
current = "Pouring Rain"
elif (current_conditions = "snowy-rainy"):
current = "Snow & Rain"
elif (current_conditions = "rainy"):
current = "Rain"
elif (current_conditions = "windy-variant"):
current = "Wind & Rain"
elif (current_conditions = "windy"):
current = "Windy"
elif (current_conditions = "fog"):
current = "Fog"
elif (current_conditions = "snowy"):
current = "Snow"
elif (current_conditions = "cloudy"):
current = "Cloudy"
elif (current_conditions = "partlycloudy"):
current = "Partly Cloudy"
elif (current_conditions = "sunny"):
current = "Sunny"
elif (current_conditions = "clear-night"):
current = "Clear Night"
else
current = "Unknown"
# return the human readable weather conditions
return current
'''
Thanks @glenngoddard
That is an interesting approach to the problem.
I notice from my graph a little spike when a clouds shade approaches and again as it leaves. I assume it is due to the extra light through the edge of the cloud adding to the direct sun. It may also be influenced by temperature changes in the sensor. I also in the past examinations of temperature differences of Tempests in different locations created formulas in python to convert the w/m2 being determined from the horizontal sensor into the actual radiation if the sensor were perpendicular to the sun. I then also calculated the amount of heat being radiated onto the vertical Tempest to help my understanding of the differences in the temperature readings from the different instruments. I used an astronomical library in python for the sun angles.
I also see more light during thin cloud combined with a clear patch for the sun and it also produces more energy than normal in my solar panels.
cheers Ian
Sorry, forgot to include calculations for solar elevation, this is needed for solar insolation.
These calculations use the stations Lat, Long, and elevation above sea level to calculate expected radiation levels without clouds.
def solar_elevation(self, latitude, longitude):
""" Return Sun Elevation in Degrees with respect to the Horizon.
Input:
Latitude in Degrees and fractional degrees
Longitude in Degrees and fractional degrees
Local Time
UTC Time
Where:
jd is Julian Date (Day of Year only), then Julian Date + Fractional True
lt is Local Time (####) 24 hour time, no colon
tz is Time Zone Offset (ie -7 from UTC)
beta is Beta for EOT
lstm is Local Standard Time Meridian
eot is Equation of Time
tc is Time Correction Factor
lst is Local Solar Time
h is Hour Angle
dec is Declination
se is Solar Elevation
** All Trigonometry Fuctions need Degrees converted to Radians **
** The assumption is made that correct local time is established **
"""
if latitude is None or longitude is None:
return None
cos = math.cos
sin = math.sin
asin = math.asin
radians = math.radians
degrees = math.degrees
jd = time.localtime(time.time()).tm_yday
hr = time.localtime(time.time()).tm_hour
min = time.localtime(time.time()).tm_min
lt = hr + min/60
tz = time.localtime(time.time()).tm_gmtoff/3600
jd = jd + lt/24
beta = (360/365) * (jd - 81)
lstm = 15 * tz
eot = (9.87*(sin(radians(beta*2)))) - (7.53*(cos(radians(beta)))) - (1.5*(sin(radians(beta))))
tc = (4 * (longitude - lstm)) + eot
lst = lt + tc/60
h = 15 * (lst - 12)
dec = cos(radians(((jd) + 10) * (360/365))) * (-23.44)
se = degrees(asin(sin(radians(latitude)) * sin(radians(dec)) + cos(radians(latitude)) * cos(radians(dec)) * cos(radians(h))))
se = round(se)
return se
I was just going to post the “Solar” graph to display Solar Insolation (predicted) and Solar Radiation (measured) along with my testing formula that I am tweaking. But…I decided you might like to see a little more. The 1st row is the only information that is not derived from the Tempest (except for soil moisture); I calculate all the temperatures, cloud/freeze levels, and Zambretti. The top of the 1st row is from the National Weather Service (only information not self-hosted) and the rest of the 1st row is from my Purple Air.
This topic had me wondering how the uv changes compared to the solar radiation when clouds are passing. Looking at the current graph of this mornings sunrise and comparing it to the timelapse cloud camera shows some differences as the sun passes different clouds. My graphs automatically adjust scaling of every parameter dependant on their actual maximum on display so the curves will not always align. If you are trying to learn how the Tempest uv and solar radiation change compared to the cloud situation you can watch my graphs and the time lapse cloud cameras on my site.
edit to add graph:
And second edit adding the video link:
Cheers Ian
Very interesting Ian. Thanks