use latest code, which does not need any calculations from persistence
I’m going to do the same thing as soon as possible!!
So your work is really really useful for me!
In final, my goal is to make OH2 alerts people in my home that the system is about to close or open the blinds …
Bye!
astro.things:
astro:sun:home [ geolocation=“XXX, XXX”, interval=300 ]
astro:moon:home [ geolocation=“XXX, XXX”, interval=300 ]
the astro binding is active
openhab> bundle:list
START LEVEL 100 , List Threshold: 50
ID │ State │ Lvl │ Version │ Name196 │ Active │ 80 │ 0.10.0.oh240 │ Eclipse SmartHome Astro Binding
weird I didn’t get it
hope you guys see something I’m missing here
really appreciate your help, thanks so far
@kriznik
yes this works but it makes my whole work with influxdb and grafana needless:unamused:
With this script I’m 2 hours behind astral.
My local time is UTC + 2, any ideas how to change the shaddow.py
@kriznik
I use the script from this post and filled the constants with values from SunCalc pointing to my location. I changed the timezone to
timezone = pytz.timezone(‘Europe/Berlin’)
But the sun position in the svg is UTC and not TZ: Europe/Berlin DST CEST. It should be UTC + 2 (due to the dst). The timezone object is localized, this should be working
for comparison
svg.
14:36:35.992 [INFO ] [clipse.smarthome.model.script.Shaddow] - 2019-07-21 14:36:35.793169
Sun azimuth: 209.804400066
Sun elevation: 54.1718247517
Moon azimuth: 354.069520686
Moon elevation: -45.2328082501
2019-07-21 14:36:35.923135
Done in 0:00:00.129966 seconds
and what exactly is wrong?
be aware that yellow dot is not representing sun position in relation to your house, but to the sky sunrise/sunset and hours.
especially at the evening it might look off, but it’s not. Shadow cast is calculated from distant light source so it’s very precise, sun is calculated from close light source because of the circle (and we know that earth is not doing perfect circle around the sun, right? )
have you changed as well
self.l = Location(('HOME', 'YOUR TOWN', LATITUDE, LONGITUDE, 'Europe/Berlin', ALTITUDE))
?
yes I have changed the parameter in the location function to my timezone (‘Europe/Berlin’).
Now checked the position of my house on SunCalc and it is not quite in the correct position as I coordinated it in shaddow.py. Ahh just 6-10 degree to the right and the rest is owed to the earth wobbling orbital ellipse.
I copied the position from google maps and it is not the same like on SunCalc.
Sorry for the trouble and thanks for physics extra lesson, I greatly appreciate it.
Keep going here, i love your great stuff
@kriznik can you please verify the self.now
varaible in shaddow.py.
self.now = timezone.localize(datetime.now(),is_dst=True)
print(self.now)
due to the fact that datetime is server datetime (utc) the varibale self.now
will always be in utc.
I guess the datetime.now() needs the timezone argument
dt_utc = datetime.datetime.now(datetime.timezone.berlin)
but I didn’t get it to work
why you have OH server in UTC?
self.now = timezone.localize(datetime.now())
self.nowUTC = datetime.utcnow()
I guess you have to adjust above code to something like
TimeZone timeZone = TimeZone.getTimeZone(“Europe/Berlin”)
self.setTimeZone(timeZone)
…
…
Are the files from the first post already updated with the new version without the need of any persistence (influxdb)?
this is the latest updated version I guess
why you have OH server in UTC?
I dont’t know, after I had setted up the system I adjusted the regional settings in Paper UI.
Seems that this isn’t enough.
Now I added to EXTRA_JAVA_OPTS = -Duser.timezone=Europe/Berlin
without success after reboot
TimeZone timeZone = TimeZone.getTimeZone(“Europe/Berlin”)
^
SyntaxError: invalid syntax
I also tried some datetime and timezone converting I found in the python libraires, without success.
I’m lost here, …again
go here and reconfigure your locale
or wait till the evening, I’ll be at my computer with proper dev tools, ipad suxx
So, I’ve been playing with my setting and no, it’s working as expected, but I’m not running server in UTC tho, anyway it should not be an issue, you can add hour from UTC if you are in Berlin to your time like this:
self.now = timezone.localize(datetime.now() + timedelta(hours=1))
^^ by playing with hours, you can see where is sun during the day without need of waiting
just jump into your server ssh and type: (adjust your path if needed)
python /etc/openhab2/scripts/shaddow.py update
refresh your browser at:
http://yourohserver-ip/static/shaddow.svg
And here is bit polished script, where you declare your timezone just once in header not twice as before:
from __future__ import print_function
import math
from datetime import datetime, timedelta, date, time
import sys
import pytz
import pylunar
from astral import Astral
from astral import Location
WIDTH = 100
HEIGHT = 100
PRIMARY_COLOR = '#1b3024'
LIGHT_COLOR = '#26bf75'
BG_COLOR = '#1a1919'
SUN_COLOR = '#ffff66'
SUN_RADIUS = 5
MOON_COLOR = '#999999'
MOON_RADIUS = 3
STROKE_WIDTH = '1'
FILENAME = '/etc/openhab2/html/shaddow.svg'
LATITUDE = 11.0000000
LONGITUDE = 12.000000
ALTITUDE = 5000.0
TIMEZONE = 'Pacific/Tongatapu'
TOWN = 'Something'
# Shape of the house in a 100 by 100 units square
SHAPE = [{'x': 16.37, 'y': 62.07}, \
{'x': 28.32, 'y': 40.16}, \
{'x': 29.57, 'y': 39.87}]
HOURS = 1
DEGS = []
## not really needed to edit anything below
class shadow(object):
"""
Shadow Object
"""
def __init__(self):
self.debug = False
self.astr = Astral()
self.l = Location(('HOME', TOWN, LATITUDE, LONGITUDE, TIMEZONE, ALTITUDE))
self.sun = self.l.sun()
timezone = pytz.timezone(TIMEZONE)
self.now = timezone.localize(datetime.now())
self.nowUTC = datetime.utcnow()
self.sun_azimuth = float(self.astr.solar_azimuth(self.now, LATITUDE, LONGITUDE))
print('Sun azimuth: ' + str(self.sun_azimuth))
self.sun_elevation = float(self.astr.solar_elevation(self.now, LATITUDE, LONGITUDE))
print('Sun elevation: ' + str(self.sun_elevation))
self.sunrise_azimuth = float(self.astr.solar_azimuth(self.sun['sunrise'], LATITUDE, LONGITUDE))
self.sunset_azimuth = float(self.astr.solar_azimuth(self.sun['sunset'], LATITUDE, LONGITUDE))
for i in xrange(0, 24, HOURS):
a = float(self.astr.solar_azimuth(timezone.localize(datetime.combine(date.today(), time(i))), LATITUDE, LONGITUDE))
if (a == None): a = 0
DEGS.extend([float(a)])
self.moon_info = pylunar.MoonInfo(self.decdeg2dms(LATITUDE), self.decdeg2dms(LONGITUDE))
self.moon_info.update(self.nowUTC)
self.moon_azimuth = self.moon_info.azimuth()
print('Moon azimuth: ' + str(self.moon_azimuth))
self.moon_elevation = self.moon_info.altitude()
print('Moon elevation: ' + str(self.moon_elevation))
if (self.sun_elevation>0):
self.elevation = self.sun_elevation
else:
self.elevation = self.moon_elevation
#
#
#
def decdeg2dms(self,dd):
negative = dd < 0
dd = abs(dd)
minutes,seconds = divmod(dd*3600,60)
degrees,minutes = divmod(minutes,60)
if negative:
if degrees > 0:
degrees = -degrees
elif minutes > 0:
minutes = -minutes
else:
seconds = -seconds
return (degrees,minutes,seconds)
#
#
#
def generatePath(self,stroke,fill,points,attrs=None):
p = ''
p = p + '<path stroke="' + stroke + '" stroke-width="' + STROKE_WIDTH + '" fill="' + fill + '" '
if (attrs != None): p = p + ' ' + attrs + ' '
p = p + ' d="'
for point in points:
if (points.index(point) == 0):
p = p + 'M' + str(point['x']) + ' ' + str(point['y'])
else:
p = p + ' L' + str(point['x']) + ' ' + str(point['y'])
p = p + '" />'
return p
#
#
#
def generateArc(self,dist,stroke,fill,start,end,attrs=None):
p = ''
try:
angle = end-start
if (angle<0):
angle = 360 + angle
p = p + '<path d="M' + str(self.degreesToPoint(start,dist)['x']) + ' ' + str(self.degreesToPoint(start,dist)['y']) + ' '
p = p + 'A' + str(dist) + ' ' + str(dist) + ' 0 '
if (angle<180):
p = p + '0 1 '
else:
p = p + '1 1 '
p = p + str(self.degreesToPoint(end,dist)['x']) + ' ' + str(self.degreesToPoint(end,dist)['y']) + '"'
p = p + ' stroke="' + stroke + '"'
if (fill != None):
p = p + ' fill="' + fill + '" '
else:
p = p + ' fill="none" '
if (attrs != None):
p = p + ' ' + attrs + ' '
else:
p = p + ' stroke-width="' + STROKE_WIDTH + '"'
p = p + ' />'
except:
p = ''
return p
#
#
#
def degreesToPoint(self,d,r):
coordinates = {'x': 0, 'y': 0}
cx = WIDTH / 2
cy = HEIGHT / 2
d2 = 180 - d
coordinates['x'] = cx + math.sin(math.radians(d2))*r
coordinates['y'] = cy + math.cos(math.radians(d2))*r
return coordinates
#
#
#
def generateSVG(self):
realSun_pos = self.degreesToPoint(self.sun_azimuth, 10000)
realMoon_pos = self.degreesToPoint(self.moon_azimuth, 10000)
if self.debug:
print(realSun_pos)
sun_pos = self.degreesToPoint(self.sun_azimuth, WIDTH / 2)
moon_pos = self.degreesToPoint(self.moon_azimuth, WIDTH / 2)
minPoint = -1
maxPoint = -1
i = 0
minAngle = 999
maxAngle = -999
if(self.sun_elevation>0):
angle_pos = sun_pos
real_pos = realSun_pos
else:
angle_pos = moon_pos
real_pos = realMoon_pos
for point in SHAPE:
#Angle of close light source
angle = -math.degrees(math.atan2(point['y']-angle_pos['y'],point['x']-angle_pos['x']))
#Angle of distant light source (e.g. sun_pos)
angle = -math.degrees(math.atan2(point['y']-real_pos['y'],point['x']-real_pos['x']))
distance = math.sqrt(math.pow(angle_pos['y']-point['y'],2) + math.pow(angle_pos['x']-point['x'],2))
if (angle<minAngle):
minAngle = angle
minPoint = i
if (angle>maxAngle):
maxAngle = angle
maxPoint = i
point['angle'] = angle
point['distance'] = distance
if self.debug:
print(str(i).ljust(10),":", str(point['x']).ljust(10), str(point['y']).ljust(10), str(round(angle,7)).ljust(10), str(round(distance)).ljust(10))
i = i + 1
if self.debug:
print("Min Point = ",minPoint)
print("Max Point = ",maxPoint)
print("")
i = minPoint
k = 0
side1Distance = 0
side2Distance = 0
side1Done = False
side2Done = False
side1 = []
side2 = []
while True:
if (side1Done == False):
side1Distance = side1Distance + SHAPE[i]['distance']
if(i != minPoint and i != maxPoint): SHAPE[i]['side'] = 1
if (i == maxPoint): side1Done = True
side1.append( { 'x': SHAPE[i]['x'], 'y': SHAPE[i]['y'] } )
if (side1Done == True):
side2Distance = side2Distance + SHAPE[i]['distance']
if(i != minPoint and i != maxPoint): SHAPE[i]['side'] = 2
if (i == minPoint): side2Done = True
side2.append( { 'x': SHAPE[i]['x'], 'y': SHAPE[i]['y'] } )
i = i + 1
if( i > len(SHAPE)-1): i = 0
if (side1Done and side2Done): break
k = k + 1
if (k == 20): break
svg = '<?xml version="1.0" encoding="utf-8"?>'
svg = svg + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
svg = svg + '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="-10 -10 120 120" xml:space="preserve">'
# background
svg = svg + '<circle cx="' + str(WIDTH/2) + '" cy="' + str(HEIGHT/2) + '" r="' + str(WIDTH/2-1) + '" fill="' + BG_COLOR + '"/>'
minPointShadowX = SHAPE[minPoint]['x'] + WIDTH * math.cos(math.radians(minAngle))
minPointShadowY = SHAPE[minPoint]['y'] - HEIGHT * math.sin(math.radians(minAngle))
maxPointShadowX = SHAPE[maxPoint]['x'] + WIDTH * math.cos(math.radians(maxAngle))
maxPointShadowY = SHAPE[maxPoint]['y'] - HEIGHT * math.sin(math.radians(maxAngle))
shadow = [ {'x': maxPointShadowX, 'y': maxPointShadowY } ] + \
side2 + \
[ {'x': minPointShadowX, 'y': minPointShadowY } ]
svg = svg + '<defs><mask id="shadowMask">'
svg = svg + ' <rect width="100%" height="100%" fill="black"/>'
svg = svg + ' <circle cx="' + str(WIDTH/2) + '" cy="' + str(HEIGHT/2) + '" r="' + str(WIDTH/2-1) + '" fill="white"/>'
svg = svg + '</mask></defs>'
svg = svg + self.generatePath('none',PRIMARY_COLOR,SHAPE)
shadow_svg = self.generatePath('none','black',shadow,'mask="url(#shadowMask)" fill-opacity="0.5"')
if (self.elevation>0):
svg = svg + self.generatePath(LIGHT_COLOR,'none',side1)
else:
svg = svg + self.generatePath(PRIMARY_COLOR,'none',side2)
if (self.elevation>0): svg = svg + shadow_svg
svg = svg + self.generateArc(WIDTH/2,PRIMARY_COLOR,'none',self.sunset_azimuth,self.sunrise_azimuth)
svg = svg + self.generateArc(WIDTH/2,LIGHT_COLOR,'none',self.sunrise_azimuth,self.sunset_azimuth)
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(self.sunrise_azimuth,WIDTH/2-2), self.degreesToPoint(self.sunrise_azimuth,WIDTH/2+2)])
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(self.sunset_azimuth,WIDTH/2-2), self.degreesToPoint(self.sunset_azimuth,WIDTH/2+2)])
for i in range(0,len(DEGS)):
if (i == len(DEGS)-1):
j = 0
else:
j = i + 1
if (i % 2 == 0):
svg = svg + self.generateArc(WIDTH/2+8,PRIMARY_COLOR,'none',DEGS[i],DEGS[j],'stroke-width="3" stroke-opacity="0.2"')
else:
svg = svg + self.generateArc(WIDTH/2+8,PRIMARY_COLOR,'none',DEGS[i],DEGS[j],'stroke-width="3" ')
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(DEGS[0],WIDTH/2+5), self.degreesToPoint(DEGS[0],WIDTH/2+11)])
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(DEGS[(len(DEGS))/2],WIDTH/2+5), self.degreesToPoint(DEGS[(len(DEGS))/2],WIDTH/2+11)])
# moon drawing: compute left and right arcs
phase = self.astr.moon_phase(self.now)
if self.debug:
print('phase: ' + str(phase))
left_radius=MOON_RADIUS
left_sweep=0
right_radius=MOON_RADIUS
right_sweep=0
if (phase > 14):
right_radius = MOON_RADIUS - (2.0*MOON_RADIUS* (1.0 - ((phase%14)*0.99 / 14.0)))
if (right_radius < 0):
right_radius = right_radius * -1.0
right_sweep = 0
else:
right_sweep = 1
if (phase < 14):
left_radius = MOON_RADIUS - (2.0*MOON_RADIUS* (1.0 - ((phase%14)*0.99 / 14.0)))
if (left_radius < 0):
left_radius = left_radius * -1.0
left_sweep = 1
if (self.moon_elevation>0):
svg = svg + '<path stroke="none" stroke-width="0" fill="' + MOON_COLOR \
+ '" d="M ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']-MOON_RADIUS) \
+ ' A ' + str(left_radius) + ' ' + str(MOON_RADIUS) + ' 0 0 ' + str(left_sweep) + ' ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']+MOON_RADIUS) \
+ ' ' + str(right_radius) + ' ' + str(MOON_RADIUS) + ' 0 0 ' + str(right_sweep) + ' ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']-MOON_RADIUS) + ' z" />'
# sun drawing
if (self.sun_elevation>0):
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS) + '" stroke="none" stroke-width="0" fill="' + SUN_COLOR + '55" />'
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS -1) + '" stroke="none" stroke-width="0" fill="' + SUN_COLOR + '99" />'
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS -2) + '" stroke="' + SUN_COLOR + '" stroke-width="0" fill="' + SUN_COLOR + '" />'
svg = svg + '</svg>'
if self.debug:
print(svg)
f = open(FILENAME, 'w')
f.write(svg)
f.close()
def main():
t1 = datetime.now()
s = shadow()
args = sys.argv
if(len(args) == 1):
dummy = 0
#print('\033[91mNo parameters specified\033[0;0m')
else:
if(args[1] == "update"):
s.generateSVG()
t2 = datetime.now()
print("Done in " + str(t2-t1) + " seconds")
if __name__ == '__main__':
main()
but still, I’ll recommend you to reconfigure your OH server to be in your timezone properly, as it’s causing issues here and there.
yes the timedelta does the trick even though you are absolutly right when server timezone is not matching the script timezone work around. I’ll set the server timezone to Europe/Berlin when I’m back home.
many thanks for your help.
I’m still wondering that adding timezone to extra_java_opts hasn’t any effect on OH
mainly because .py file is python and not interacting with OH in any way. Its standalone script, which is being called from OH.
correct time is everything, otherwise you’ll have to care about daylight saving manually and edit timedelta eventually.
@kriznik - your script looks great and working for me (kind of) - I am in the southern hempsphere - what can I change to fix the sun position?
all you really need to do is to change these
LATITUDE = 11.0000000
LONGITUDE = 12.000000
ALTITUDE = 5000.0
TIMEZONE = 'Pacific/Tongatapu'
SHAPE = xxx
SHAPE is most difficult, really
+have correct time on your machine.
Rest will magically happen
it’s not originally my script, I’ve just made few adjustments/fixes, credits belongs to others mainly
But indeed thanks
I found this tool for plotting points on a image to get the SHAPE
https://www.mobilefish.com/services/record_mouse_coordinates/record_mouse_coordinates.php
I used google maps to get a north aligned floorplan, also i had to manually scale down the output plots and easy left right up down increments to centre for the final gui
damn this is good, I wish I knew before I did my house point by point manually from Inkscape