Show Current Sun Position and Shadow of House (Generate SVG)

it cant create svg file, check permission in your directory

oh god …
i was so blind for 2 h now.
but even before you posted i did a final check on my shaddow.py. the path for raspberry was still there i changed it for my setup and it worked. Sorry for being so blind …

Now i’ll try implementing my house :wink: clockwise, i kow :wink:

yeah, it’s called code blindness… we all have that time to time and that’s why forums are for :wink:

my house is exactly 90° off x)
maybe it’s the starting point? the lines and so on are colored correctly. i went counterclockwise because when going clockwise the lines facing the sun were not highlighted but the shadow side was.

EDIT: not 90° off but mirrored to the horizontal i guess. - strange

I don’t get it. I tried using different starting points and both clockwise and counterclockwise drawing … I started counting the coordinates in the bottom left corner. I will try top left now.


Finally it works!

How did you implement this widget into the matrix theme? Which container did you use?

Hello!

i moved to a virtual box and the script is not working anymore. I set up a Ubuntu Server Virtual Box and almost anything is running fine.
Now i try to implement this script and i am not able to get it working.
I installed pylunar as well as pytz but the script fails to import them:

2019-12-19 11:11:56.807 [INFO ] [lipse.smarthome.model.script.Shaddow] - Traceback (most recent call last):
  File "/etc/openhab2/scripts/shaddow.py", line 7, in <module>
    import pylunar
ImportError: No module named pylunar

I did not create a openhab user on the ubuntu machine. is this the problem?

Thanks

Hi,

What is the reason that there are two time definitions (now and nowUTC) in the code from August 2019 which are used for sun and moon differently?

Thanks a lot and best greetings

because moon part (pylunar) is using utc time by it’s design

My Openhab runs inside a Docker container which is running on my OpenMediaVault server. I keep getting the following error:

Execution failed (Exit value: -559038737. Caused by java.io.IOException: Cannot run program “/usr/bin/python” (in directory “.”): error=2, No such file or directory)

I’ve checked the host and the directory does indeed exist. Any suggestions? Thanks

there is a difference between host and container inside host … connect to your docker container as root and do

apt update
apt install python python-pip
pip install pytz pylunar astral

these are required for script to work, and you have to install them INSIDE your docker container where openhab is running, not on host

I’m not exceptionally talented with linux, or docker for that matter, but I love it when you guys provide helpful information and I can figure out the task of doing what you recommend. After accidentally installing another container of openhab by using the command “docker run -it [myimage]” I reversed course and discovered I could access my current container with Portainer. I opened a console and did what you recommended. LOL, I don’t know if it solved my problem yet, but I am damn proud to have gotten this far! That final line of instruction takes awhile to complete. Thanks kriznik!

glad to help :wink:

I would like to try this myself, but I’m not sure what I need.
How will the script (Show Current Sun Position and Shadow of House (Generate SVG)) launch? Do I need the rules from the first post?

GUIDELINE!
whomever wants to use python3 this is updated version of the script for python3

SHELL
install dependencies

apt update && apt install python3 python3-pip -y
python3 -m pip install astral==1.10.1 pytz pylunar

if you accidentally upgraded astral to 2.1 you need to do

python3 -m pip uninstall astral
python3 -m pip install astral==1.10.1

SCRIPT
(python3)

nano openhab/conf/scripts/shadow.py

note: follow inscript SETTING!! you have to define your OWN coordinates of the house, timezone etc.!!!

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 = '/openhab/conf/html/shaddow.svg'

## REQUIRED CONFIGURATION
LATITUDE = YOUR LAT
LONGITUDE = YOUR LON
ALTITUDE = 0.0
TIMEZONE = 'YOUR/TIMEZONE'
TOWN = 'YOUR TOWN'

# Shape of the house in a 100 by 100 units square
SHAPE = [{'x': 1, 'y': 2}, \
		{'x': 1, 'y': 2}, \
		{'x': 1, 'y': 2}]

	
## DO NOT EDIT ANYTHING BELOW
HOURS = 1
DEGS = []

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.now = timezone.localize(datetime.now() + timedelta(hours=7) + timedelta(minutes=30))
		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 range(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)])

			if self.debug:
				print(LATITUDE)

		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,orig_start,orig_end,attrs=None):
		
		if(LATITUDE<0):
			start = orig_end
			end = orig_start
		else:
			start = orig_start
			end = orig_end

		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("")
			print("real sun position: " + str(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

		if self.debug:
			print("")
			print("House:")

		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), "angle: ", str(round(angle,7)).ljust(10), "dist: ", str(round(distance)).ljust(10))
			i = i + 1

		if self.debug: 
			print("")
			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"')

			if self.debug:
				print(DEGS[i])


		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()

RULES

/* shadow SVG */
rule "Generate shaddow SVG"
when
    Item Sun_Azimuth received update
then
    executeCommandLine("python3 /openhab/conf/scripts/shadow.py update", 10000)
end
1 Like

Help please
input python /etc/openhab2/scripts/shaddow.py update
Output a = self.oh.getStateHistoryFromInflux (‘Azimuth’, t.strftime (’% Y-% m-% dT% H:% M:% S’) + ‘Z’)
^
IndentationError: unexpected indent

Help please
Successfully got state from OpenHab: Elevation

Successfully got state from OpenHab: Sunrise_Azimuth

Successfully got state from OpenHab: Sunset_Azimuth

Successfully got state from OpenHab: Moon_Azimuth

Successfully got state from OpenHab: Moon_Elevation

Successfully got data from InfluxDB

Traceback (most recent call last):

File “/etc/openhab2/scripts/shaddow1.py”, line 269, in

main()

File “/etc/openhab2/scripts/shaddow1.py”, line 263, in main

s.generateSVG()

File “/etc/openhab2/scripts/shaddow1.py”, line 118, in generateSVG

realSun = self.degreesToPoint(self.azimuth, 10000)

File “/etc/openhab2/scripts/shaddow1.py”, line 110, in degreesToPoint

d2 = 180 - d

TypeError: unsupported operand type(s) for -: ‘int’ and ‘str’

you are using outdated version which works with persistence, can’t help you on that.

Openhab 2.51 or PI
I got it going

Help please see pictureshaddow

Hi Jonas,

How did you solve the horizontal mirrored house?
I tried to subtract the y from 100. Now the house seems ok, shadow is ok, but the lighter green line is wrong.

Thanks