extremely hard. is my 4th attempt trying to make this work by following step by step the articles provided , plenty of missing points, plenty of confusion, various of solutions messes up the initial setup . no offence, i really appreciate people’s work but this is extremely fustrating . for example: the op provided 2 scripts , myopenhab.py seems to serve no purpose as the user did not added any mention about it and it’s execution … maybe i’m too stupid for this but it’s enough for me …
OH does not provide any script to generate svg of your house to my knowledge.
This guide expects you are already familiar with OH, folder structure etc. shell, python and stuff, it is not simple plug & play
but for people which do not want to read and learn, I’ve added some additional info to my python3 version post. It does include every info you need to get this working.
hello together, the idea of this is so great, that i finally created an account and i want to show my solution.
I have made an different approach, since i have no persistence yet and thougt, it must be possible to get the sun position by calculating it, with the astro binding and i don’t wanted to wait a whole day, to fill the database. I’m also not familiar with python, so i wanted a solution within a rule.
Here is the result, just a bunch of rules, some items and a standard picture in habpanel
(The rule for generating the svg is about 500 lines of code, so it takes a lot of time for openhab to update it)
Group gAstro "Astro Items"
DateTime ActualTime "aktuelle Uhrzeit" (gAstro) {channel="ntp:ntp:local:dateTime"}
DateTime RiseStart "Startzeit Sonnenaufgang" (gAstro) {channel="astro:sun:home:rise#start"}
DateTime SetEnd "Endzeit Sonnenuntergang" (gAstro) {channel="astro:sun:home:set#end"}
Number:Angle PositionAzimuth "Azimut" (gAstro) {channel="astro:sun:home:position#azimuth"}
Number:Angle SunElevation "Elevation" (gAstro) {channel="astro:sun:home:position#elevation"}
Number:Angle SunrisePositionAzimuth "Sonnenaufgang Azimuth" (gAstro)
Number:Angle SunsetPositionAzimuth "Sonnenuntergang Azimuth" (gAstro)
Number:Angle Azimuth0 "Azimuth 00:00 Uhr" (gAstro)
Number:Angle Azimuth1 "Azimuth 01:00 Uhr" (gAstro)
Number:Angle Azimuth2 "Azimuth 02:00 Uhr" (gAstro)
Number:Angle Azimuth3 "Azimuth 03:00 Uhr" (gAstro)
Number:Angle Azimuth4 "Azimuth 04:00 Uhr" (gAstro)
Number:Angle Azimuth5 "Azimuth 05:00 Uhr" (gAstro)
Number:Angle Azimuth6 "Azimuth 06:00 Uhr" (gAstro)
Number:Angle Azimuth7 "Azimuth 07:00 Uhr" (gAstro)
Number:Angle Azimuth8 "Azimuth 08:00 Uhr" (gAstro)
Number:Angle Azimuth9 "Azimuth 09:00 Uhr" (gAstro)
Number:Angle Azimuth10 "Azimuth 10:00 Uhr" (gAstro)
Number:Angle Azimuth11 "Azimuth 11:00 Uhr" (gAstro)
Number:Angle Azimuth12 "Azimuth 12:00 Uhr" (gAstro)
Number:Angle Azimuth13 "Azimuth 13:00 Uhr" (gAstro)
Number:Angle Azimuth14 "Azimuth 14:00 Uhr" (gAstro)
Number:Angle Azimuth15 "Azimuth 15:00 Uhr" (gAstro)
Number:Angle Azimuth16 "Azimuth 16:00 Uhr" (gAstro)
Number:Angle Azimuth17 "Azimuth 17:00 Uhr" (gAstro)
Number:Angle Azimuth18 "Azimuth 18:00 Uhr" (gAstro)
Number:Angle Azimuth19 "Azimuth 19:00 Uhr" (gAstro)
Number:Angle Azimuth20 "Azimuth 20:00 Uhr" (gAstro)
Number:Angle Azimuth21 "Azimuth 21:00 Uhr" (gAstro)
Number:Angle Azimuth22 "Azimuth 22:00 Uhr" (gAstro)
Number:Angle Azimuth23 "Azimuth 23:00 Uhr" (gAstro)
Switch UpdateAzimuth "Update Azimuth" (gAstro)
Switch UpdateSVG "Update SVG" (gAstro)
import java.time.ZonedDateTime
rule "Get Sunrise Position Azimut"
when
Channel "astro:sun:home:rise#event" triggered START
then
SunrisePositionAzimuth = PositionAzimuth
end
rule "Get Sunset Position Azimut"
when
Channel "astro:sun:home:set#event" triggered END
then
SunsetPositionAzimuth = PositionAzimuth
end
rule "System Start"
when
System started
then
UpdateAzimuth.sendCommand(ON)
end
rule "Update SVG when Azimuth Changed"
when
Item PositionAzimuth received update
then
UpdateSVG.sendCommand(ON)
end
rule "Update Azimuths once a day"
when
Time is midnight
then
UpdateAzimuth.sendCommand(ON)
end
rule "Update Azimuth"
when
Item UpdateAzimuth received command ON
then
val sunActions = getActions("astro","astro:sun:home")
if(null === sunActions) {
logInfo("actions", "sunActions not found, check thing ID")
return
} else {
val sunSetEvent = "SUN_SET"
val sunRiseEvent = "SUN_RISE"
val today = ZonedDateTime.now
val sunSetEventTime = sunActions.getEventTime(sunSetEvent,today,"START")
val sunRiseEventTime = sunActions.getEventTime(sunRiseEvent,today,"START")
val sunSetAzimuth = sunActions.getAzimuth(sunSetEventTime)
val sunRiseAzimuth = sunActions.getAzimuth(sunRiseEventTime)
SunsetPositionAzimuth.sendCommand(sunSetAzimuth)
SunrisePositionAzimuth.sendCommand(sunRiseAzimuth)
val zdt = ZonedDateTime.now
Azimuth0.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 0, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth1.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 1, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth2.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 2, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth3.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 3, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth4.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 4, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth5.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 5, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth6.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 6, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth7.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 7, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth8.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 8, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth9.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 9, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth10.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 10, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth11.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 11, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth12.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 12, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth13.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 13, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth14.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 14, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth15.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 15, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth16.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 16, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth17.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 17, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth18.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 18, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth19.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 19, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth20.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 20, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth21.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 21, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth22.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 22, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
Azimuth23.sendCommand(sunActions.getAzimuth(ZonedDateTime.of(zdt.getYear, zdt.getMonthValue, zdt.getDayOfMonth(), 23, 0, 0, 0, zdt.getZone())).toBigDecimal as Number)
}
UpdateAzimuth.sendCommand(OFF)
end
// =====================================================================================================================================
// Rule generiert die Vektorgrafik für den Hausgrundriss mit Sonnestand und Schatten
// =====================================================================================================================================
import java.io.File
import java.io.FileWriter
import java.lang.Math
import java.awt.Point
rule "Update Vectorgrafik"
when
Item UpdateSVG received command ON
then
val WIDTH = 100
val HEIGHT = 100
val LIGHT_COLOR = "#4ebbed" //"#1b3024" // hellblau
val PRIMARY_COLOR = "#34485b" //Hellgrau //"#253443" Hintergrundfabre Buttons
val STROKE_WIDTH = "1"
val doctype = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
val header ="<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\" height=\"250\" width=\"250\">\n"
val footer = "\n</svg>"
val SHAPE = newArrayList()
SHAPE.add(new Point (3920, 680)) // 1. oben links
SHAPE.add(new Point (6410, 510)) // 2. oben rechts
SHAPE.add(new Point (6540, 2320))
SHAPE.add(new Point (6640, 2310))
SHAPE.add(new Point (6720, 3310))
SHAPE.add(new Point (6620, 3310))
SHAPE.add(new Point (6700, 4510))
SHAPE.add(new Point (5750, 4570))
SHAPE.add(new Point (5980, 7620))
SHAPE.add(new Point (6050, 7610)) // 10 Eingang Haus 2 oben rechts außen
SHAPE.add(new Point (6080, 8050)) // 11 Eingang Haus 2 unten rechts außen
SHAPE.add(new Point (6000, 8060))
SHAPE.add(new Point (6070, 9330)) // 13 unten rechts
SHAPE.add(new Point (3600, 9500)) // 14. unten links
SHAPE.add(new Point (3270, 4660))
SHAPE.add(new Point (4190, 4620))
var sunX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(PositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2)
var sunY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(PositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2)
var svg = ""
svg += "<defs><mask id=\"shaddowMask\">\n"
svg += " <rect width=\"100%\" height=\"100%\" fill=\"black\"/>\n"
svg += " <circle cx=\"" + WIDTH/2 + "\" cy=\"" + (HEIGHT/2) + "\" r=\"" + (WIDTH/2-1) + "\" fill=\"white\"/>\n"
svg += "</mask></defs>\n"
// =====================================================================================================================================
// Schatten berechnen
// =====================================================================================================================================
var minPoint = -1
var maxPoint = -1
var minAngle = 999.0
var maxAngle = -999.0
var winkel = 0.0
var abstand = newArrayList()
var tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(PositionAzimuth.state.toString.substring(0, 5)))) * 10000
var tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(PositionAzimuth.state.toString.substring(0, 5)))) * 10000
var Point pSonne = new Point(tmpX.intValue(), tmpY.intValue())
for(var i = 0; i < SHAPE.size() ; i++)
{
// Angle of distant light source (e.g. sun)
winkel = -Math.toDegrees(Math.atan2(((SHAPE.get(i).getY()/100) - pSonne.getY()), ((SHAPE.get(i).getX()/100) - pSonne.getX())))
abstand.add(Math.sqrt(Math.pow(pSonne.getY() - (SHAPE.get(i).getY()/100), 2) + Math.pow(pSonne.getX() - (SHAPE.get(i).getX()/100), 2)))
if(winkel < minAngle)
{
minAngle = winkel
minPoint = i
}
if(winkel > maxAngle)
{
maxAngle = winkel
maxPoint = i
}
}
var side1Distance = 0.0
var side2Distance = 0.0
var side1Done = false
var side2Done = false
var side1 = newArrayList()
var side2 = newArrayList()
var whileNotDone = true
var k = minPoint
var l = 0
while(whileNotDone)
{
if(side1Done == false)
{
side1Distance += abstand.get(k)
if(k == maxPoint)
{
side1Done = true
}
side1.add(SHAPE.get(k))
}
if(side1Done == true)
{
side2Distance += abstand.get(k)
if(k == minPoint)
{
side2Done = true
}
side2.add(SHAPE.get(k))
}
k += 1
if(k > (SHAPE.size() - 1))
{
k = 0
}
if((side1Done && side2Done) == true)
{
whileNotDone = false
}
l += 1
if(l >= 40)
{
whileNotDone = false
logInfo("Debug", "l = " + l)
}
}
val minPointShaddowX = (((SHAPE.get(minPoint).getX()/100) + WIDTH * Math.cos(Math.toRadians(minAngle)))) * 100
val minPointShaddowY = (((SHAPE.get(minPoint).getY()/100) - HEIGHT * Math.sin(Math.toRadians(minAngle)))) * 100
val maxPointShaddowX = (((SHAPE.get(maxPoint).getX()/100) + WIDTH * Math.cos(Math.toRadians(maxAngle)))) * 100
val maxPointShaddowY = (((SHAPE.get(maxPoint).getY()/100) - HEIGHT * Math.sin(Math.toRadians(maxAngle)))) * 100
val shaddow = newArrayList()
shaddow.add(new Point(maxPointShaddowX.intValue(), maxPointShaddowY.intValue()))
for(var i = 0; i < side2.size(); i++)
{
shaddow.add(side2.get(i))
}
shaddow.add(new Point(minPointShaddowX.intValue(), minPointShaddowY.intValue()))
// =====================================================================================================================================
// Schatten
// =====================================================================================================================================
var svgPath = ""
svgPath += "<path stroke=\"none\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"black\" mask=\"url(#shaddowMask)\" fill-opacity=\"0.5\" "
svgPath += "d=\""
for(var i = 0; i < shaddow.size() ; i++)
{
if(i==0)
{
svgPath += "M" + (shaddow.get(i).getX()/100) + " " + (shaddow.get(i).getY()/100)
}
else
{
svgPath += " L" + (shaddow.get(i).getX()/100) + " " + (shaddow.get(i).getY()/100)
}
}
svgPath += "\" />\n"
// =====================================================================================================================================
// Schatten nur wenn Sonne da ist
// =====================================================================================================================================
val ele = Double.parseDouble(SunElevation.state.toString.substring(0, 5))
var tmpFarbe = PRIMARY_COLOR
if(ele > 0)
{
svg += svgPath
tmpFarbe = LIGHT_COLOR
}
// =====================================================================================================================================
// Beleuchtete Seite einfärben
// =====================================================================================================================================
svgPath = "<path stroke=\"" + tmpFarbe + "\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" d=\""
for(var i = 0; i < side1.size() ; i++)
{
if(i==0)
{
svgPath += "M" + (side1.get(i).getX()/100) + " " + (side1.get(i).getY()/100)
}
else
{
svgPath += " L" + (side1.get(i).getX()/100) + " " + (side1.get(i).getY()/100)
}
}
svgPath += "\" />\n"
svg += svgPath
// =====================================================================================================================================
// Haus Umriss
// =====================================================================================================================================
svgPath = "<path stroke=\"none\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"" + PRIMARY_COLOR + "\" d=\""
for(var i = 0; i < SHAPE.size() ; i++)
{
if(i==0)
{
svgPath += "M" + (SHAPE.get(i).getX()/100) + " " + (SHAPE.get(i).getY()/100)
}
else
{
svgPath += " L" + (SHAPE.get(i).getX()/100) + " " + (SHAPE.get(i).getY()/100)
}
}
svgPath += "\" />\n"
svg += svgPath
// =====================================================================================================================================
// Bogen "Nacht" erzeugen
// =====================================================================================================================================
var arch = ""
var anfang = Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5))
var ende = Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5))
var bogenlaenge = ende - anfang
if(bogenlaenge < 0)
{
bogenlaenge += 360
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - anfang)) * (WIDTH / 2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - anfang)) * (WIDTH / 2)
arch += "<path d=\"M" + tmpX + " " + tmpY + " "
arch += "A" + (WIDTH / 2) + " " + (WIDTH / 2) + " 0 "
if(bogenlaenge < 180)
{
arch += "0 1 "
}
else
{
arch += "1 1 "
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - ende)) * (WIDTH / 2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - ende)) * (WIDTH / 2)
arch += tmpX + " " + tmpY + "\" stroke=\"" + PRIMARY_COLOR + "\""
arch += " stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" />\n"
svg += arch
// =====================================================================================================================================
// Bogen "Tag" erzeugen
// =====================================================================================================================================
arch = ""
anfang = Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5))
ende = Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5))
bogenlaenge = ende - anfang
if(bogenlaenge < 0)
{
bogenlaenge += 360
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - anfang)) * (WIDTH / 2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - anfang)) * (WIDTH / 2)
arch += "<path d=\"M" + tmpX + " " + tmpY + " "
arch += "A" + (WIDTH / 2) + " " + (WIDTH / 2) + " 0 "
if(bogenlaenge < 180)
{
arch += "0 1 "
}
else
{
arch += "1 1 "
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - ende)) * (WIDTH / 2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - ende)) * (WIDTH / 2)
arch += tmpX + " " + tmpY + "\" stroke=\"" + LIGHT_COLOR + "\""
arch += " stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" />\n"
svg += arch
// =====================================================================================================================================
// Markierung Sonnenaufgang
// =====================================================================================================================================
svgPath += "<path stroke=\"" + LIGHT_COLOR +"\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" d=\""
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2-2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2-2)
svgPath += "M" + tmpX + " " + tmpY
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2+2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(SunrisePositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2+2)
svgPath += " L" + tmpX + " " + tmpY +"\" />\n"
svg += svgPath
// =====================================================================================================================================
// Markierung Sonnenuntergang
// =====================================================================================================================================
svgPath = "<path stroke=\"" + LIGHT_COLOR +"\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" d=\""
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2-2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2-2)
svgPath += "M" + tmpX + " " + tmpY
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2+2)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(SunsetPositionAzimuth.state.toString.substring(0, 5)))) * (WIDTH / 2+2)
svgPath += " L" + tmpX + " " + tmpY +"\" />\n"
svg += svgPath
// =====================================================================================================================================
// Stundenkreis
// =====================================================================================================================================
var abschnitt = newArrayList()
abschnitt.add(Azimuth0.state.toString.substring(0, 5))
abschnitt.add(Azimuth1.state.toString.substring(0, 5))
abschnitt.add(Azimuth2.state.toString.substring(0, 5))
abschnitt.add(Azimuth3.state.toString.substring(0, 5))
abschnitt.add(Azimuth4.state.toString.substring(0, 5))
abschnitt.add(Azimuth5.state.toString.substring(0, 5))
abschnitt.add(Azimuth6.state.toString.substring(0, 5))
abschnitt.add(Azimuth7.state.toString.substring(0, 5))
abschnitt.add(Azimuth8.state.toString.substring(0, 5))
abschnitt.add(Azimuth9.state.toString.substring(0, 5))
abschnitt.add(Azimuth10.state.toString.substring(0, 5))
abschnitt.add(Azimuth11.state.toString.substring(0, 5))
abschnitt.add(Azimuth12.state.toString.substring(0, 5))
abschnitt.add(Azimuth13.state.toString.substring(0, 5))
abschnitt.add(Azimuth14.state.toString.substring(0, 5))
abschnitt.add(Azimuth15.state.toString.substring(0, 5))
abschnitt.add(Azimuth16.state.toString.substring(0, 5))
abschnitt.add(Azimuth17.state.toString.substring(0, 5))
abschnitt.add(Azimuth18.state.toString.substring(0, 5))
abschnitt.add(Azimuth19.state.toString.substring(0, 5))
abschnitt.add(Azimuth20.state.toString.substring(0, 5))
abschnitt.add(Azimuth21.state.toString.substring(0, 5))
abschnitt.add(Azimuth22.state.toString.substring(0, 5))
abschnitt.add(Azimuth23.state.toString.substring(0, 5))
var j = 0
for(var i = 0; i < abschnitt.size() ; i++)
{
if(i == (abschnitt.size() -1))
{
j = 0
}
else
{
j = i +1
}
if(i % 2 == 0)
{
arch = ""
anfang = Double.parseDouble(abschnitt.get(i))
ende = Double.parseDouble(abschnitt.get(j))
bogenlaenge = ende - anfang
if(bogenlaenge < 0)
{
bogenlaenge += 360
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - anfang)) * (WIDTH / 2+8)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - anfang)) * (WIDTH / 2+8)
arch += "<path d=\"M" + tmpX + " " + tmpY + " "
arch += "A" + (WIDTH / 2) + " " + (WIDTH / 2) + " 0 "
if(bogenlaenge < 180)
{
arch += "0 1 "
}
else
{
arch += "1 1 "
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - ende)) * (WIDTH / 2+8)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - ende)) * (WIDTH / 2+8)
arch += tmpX + " " + tmpY + "\" stroke=\"" + PRIMARY_COLOR + "\""
arch += " stroke-width=\"3\" fill=\"none\" stroke-opacity=\"0.3\"" + " />\n"
svg += arch
}
else
{
arch = ""
anfang = Double.parseDouble(abschnitt.get(i))
ende = Double.parseDouble(abschnitt.get(j))
bogenlaenge = ende - anfang
if(bogenlaenge < 0)
{
bogenlaenge += 360
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - anfang)) * (WIDTH / 2+8)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - anfang)) * (WIDTH / 2+8)
arch += "<path d=\"M" + tmpX + " " + tmpY + " "
arch += "A" + (WIDTH / 2) + " " + (WIDTH / 2) + " 0 "
if(bogenlaenge < 180)
{
arch += "0 1 "
}
else
{
arch += "1 1 "
}
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - ende)) * (WIDTH / 2+8)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - ende)) * (WIDTH / 2+8)
arch += tmpX + " " + tmpY + "\" stroke=\"" + PRIMARY_COLOR + "\""
arch += " stroke-width=\"3\" fill=\"none\" " + " />\n"
svg += arch
}
}
// =====================================================================================================================================
// Markierung Mitternacht
// =====================================================================================================================================
svgPath = "<path stroke=\"" + LIGHT_COLOR +"\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" d=\""
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(Azimuth0.state.toString.substring(0, 5)))) * (WIDTH / 2+5)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(Azimuth0.state.toString.substring(0, 5)))) * (WIDTH / 2+5)
svgPath += "M" + tmpX + " " + tmpY
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(Azimuth0.state.toString.substring(0, 5)))) * (WIDTH / 2+11)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(Azimuth0.state.toString.substring(0, 5)))) * (WIDTH / 2+11)
svgPath += " L" + tmpX + " " + tmpY + "\" />\n"
svg += svgPath
// =====================================================================================================================================
// Markierung Mittag
// =====================================================================================================================================
svgPath = "<path stroke=\"" + LIGHT_COLOR +"\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"none\" d=\""
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(Azimuth12.state.toString.substring(0, 5)))) * (WIDTH / 2+5)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(Azimuth12.state.toString.substring(0, 5)))) * (WIDTH / 2+5)
svgPath += "M" + tmpX + " " + tmpY
tmpX = (WIDTH / 2) + Math.sin(Math.toRadians(180 - Double.parseDouble(Azimuth12.state.toString.substring(0, 5)))) * (WIDTH / 2+11)
tmpY = (HEIGHT / 2) + Math.cos(Math.toRadians(180 - Double.parseDouble(Azimuth12.state.toString.substring(0, 5)))) * (WIDTH / 2+11)
svgPath += " L" + tmpX + " " + tmpY + "\" />\n"
svg += svgPath
// =====================================================================================================================================
// Sonnenposition
// =====================================================================================================================================
svg += "<circle cx=\"" + sunX + "\" cy=\"" + sunY + "\" r=\"3\" stroke=\"" + LIGHT_COLOR + "\" stroke-width=\"" + STROKE_WIDTH + "\" fill=\"" + LIGHT_COLOR + "\" />\n"
val datei = new File("/etc/openhab2/html/shadow.svg")
val dateiwriter = new FileWriter(datei, false)
dateiwriter.write(doctype + header + svg + footer)
dateiwriter.flush()
dateiwriter.close()
UpdateSVG.sendCommand(OFF)
end
since im new, i can only add one upload per post, so here is my svg
Hello, has someone managed to run the script under OH3?
because it is python, it will run in OH3 without any issues… as it is calculated outside of OHx anyways…
Yes, it works, but a few adjustments need to be made.
Thanks to the original author for this script, it has been on all my tablets for many years now and is appreciated by the whole family.
I wonder if there is any licence to be put on this? I have included my modified copy (Python3, blackening etc) on pyrotun/houseshadow.py at master · berland/pyrotun · GitHub
I put GPLv3 on this github reposity, but would gladly correct it for this particular file according to the primary authors wishes.
For feature requests, I would like to display the rise and dawn times in a small font next to the rise and dawn tickmarks. Will try and see if I can get my head around the svg code.
I think the original license was the MIT License according to the GitHub repository where the original author published the script.
Another fork
Many thanks to the original author for this insane script.
A very cool nice-to-have on my HABPanel.
Changes in this fork
- Python 3 compatible
- using the python-openhab library
- using InfluxDB v2‘s FLUX language with the official influxdb-client library
- showing the current position of the moon
Download
The script and the How-To-Use can be found here:
GitHub: florian-h05/openhab-conf
Hello,
I am using this script over years with my OH 3 instance.
Now I have moved to a new system and I am struggeling to get the script running again.
I am faced with the following error messages:
PI4 with Raspberry PI OS (with debian bookworm).
Can somebody help?
Thanks,
Tobi
Which one do you use? there are several forks of the script.
Post #257 march 2020
Script und guidelines posted by kriznik.
Well, as the original script is only 379 lines long (and python complains about line 383…)
and in the original script there is a line 66 with
for i in range(0, 24, HOURS):
I guess you throw some text in the file accidentally.
@togi
here is my script iteration (fairly same as in the post you mentioned but with wind direction as well and few little details), running it for years (OH4 now)
only slight issue with OH4 and basic sitemap UI is it’s not always refreshing self on UI for some reason, but svg is up to date correctly
about you issue:
xrange
is not even in the script, so you have typo there.
anyway make sure you have correctly defined SHAPE and you have these:
astral==1.10.1
pylunar
I was not rewriting it to the latest astral, as this just works
calling the script via rule:
rule "Updater: python: Shadow SVG"
when
Item Sun_Azimuth received update or
Item WS_WindAngle received update or
System started
then
executeCommandLine("python3", "/etc/openhab/scripts/shadow.py", "update", (WS_WindAngle.state as Number).floatValue.toString)
// debug
/*
val resp = executeCommandLine(Duration.ofSeconds(2), "python3", "/etc/openhab/scripts/shadow.py", "update", (WS_WindAngle.state as Number).floatValue.toString)
logInfo("Shaddow", "Updating Shaddow SVG")
logInfo("Shaddow", resp)
logInfo("Shaddow", (WS_WindAngle.state as Number).floatValue.toString)
*/
end
script:
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'
WIND_COLOR = '#52b4bf'
SUN_RADIUS = 5
MOON_COLOR = '#999999'
MOON_RADIUS = 3
STROKE_WIDTH = '1'
FILENAME = '/etc/openhab/html/shadow.svg'
LATITUDE = 23.555555
LONGITUDE = 23.7777777
ALTITUDE = 371.0
TIMEZONE = 'Europe/Prague'
TOWN = 'Prague'
# 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}, \
{'x': 36.16, 'y': 28.41}, \
{'x': 35.51, 'y': 27.74}, \
{'x': 35.63, 'y': 15.48}, \
{'x': 46.14, 'y': 9.63}, \
{'x': 56.50, 'y': 16.13}, \
{'x': 56.63, 'y': 22.21}, \
{'x': 69.77, 'y': 28.99}, \
{'x': 69.99, 'y': 28.78}, \
{'x': 85.67, 'y': 37.63}, \
{'x': 67.10, 'y': 70.33}, \
{'x': 66.09, 'y': 70.21}, \
{'x': 58.27, 'y': 83.97}, \
{'x': 39.55, 'y': 73.58}, \
{'x': 38.68, 'y': 74.68}]
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.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,width=None):
swith = STROKE_WIDTH
if (width != None): swith = width
p = ''
p = p + '<path stroke="' + stroke + '" stroke-width="' + swith + '" 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, wind_azimuth=0):
wind_azimuth = float(wind_azimuth)
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)
wind_pos = self.degreesToPoint(wind_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 + '" />'
#wind
svg = svg + self.generatePath(WIND_COLOR,'none',[self.degreesToPoint(wind_azimuth,WIDTH//2+5), self.degreesToPoint(wind_azimuth,WIDTH//2+20)],"","2")
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
#print(len(args))
#print((args))
if(len(args) == 1):
dummy = 0
#print('\033[91mNo parameters specified\033[0;0m')
else:
if(args[1] == "update"):
s.generateSVG(args[2])
t2 = datetime.now()
print("Done in " + str(t2-t1) + " seconds")
if __name__ == '__main__':
main()
Thank you very much.
Script is working again, and BTW, the addtional wind direction indicator is cool.
Because of BasicUI has not been automatically updated with webview anymore, I found out how to do it with Image element instead.
All you need is to update script and install few thing
apt update
apt install libcairo2 -y
pip install cairosvg
then adjust script little bit
import cairosvg
....
FILENAME = '/etc/openhab/html/shadow.svg'
FILEPNG = '/etc/openhab/html/shadow.png'
....
....
....
if(args[1] == "update"):
s.generateSVG(args[2])
cairosvg.svg2png(url=FILENAME,write_to=FILEPNG,scale=1.2)
then in sitemap file change
Webview url="/static/shadow.svg?{{itemValue('Sun_Randomizer')}}" icon="none"
to
Image url="https://<url of the openhab>/static/shadow.png" refresh=60000
and you’ve got refreshing image.
note: I can see some imperfections in the png which are not in the svg, so might try different library in the future.
well I found out why
Image url="https://<url of the openhab>/static/shadow.svg" refresh=60000
was not working correctly, so you can fix the script instead and no need to generate png from svg
change this:
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">'
to this
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" width="150px" height="150px">'
Hi,
could this feature be embedded into the OH, to have it working natively as a feature of Main UI pages, lets say Home Plan page…
or, can we use something like https://app.shadowmap.org/ or https://shademap.app/ or something similar instead of openstreetmap when displaying a house on the map?
I see some built in options to select providers of overlay and background tiles, but couldn’t get them to work…