Design your SVG floorplan or dashboard for HABPanel with Inkscape

Thank you all very much for this tutorial - I have something working, with a rough outline of my house and my first lightbulb icon controlling a switch! When I tried to add more, I ran into a problem…

I moved the svg file on my server to floorplan.svg.old, and uploaded a new version to my server that I saved from inkscape. When I reloaded habpanel, I still have the previous version of the svg file. I tried restarting openhab2, but it still shows the old version of the floorplan. Any idea where my problem might be?

I have also had this problem.

I guess it’s a cache issue somewhere, but gave up after 5 minutes. Instead, I just save with a new name, and adjust the reference to the SVG file in HABPanel to point to the new name. This is a minor annoyance during development, but it’s a workaround…!

I’ve found the clearing the cache usually fixes this. Occasionally I need to restart the browser as well.

This problem is about the browser cache. The new file is not pulled again, instead the browser uses the svg already loaded.

Harry

Thank you all - copying the URL, closing the browser tab, and opening a new tab seems to have fixed the problem. Back to the matter of the day - getting light bulb icons to change color…

I did a little research and came up with this keyboard navigation that works on Habpanel using AngularJS. https://stackoverflow.com/questions/18086865/angularjs-move-focus-to-next-control-on-enter
https://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field?rq=1
You can navigate the grid of items using the arrow keys. You also have to make your items focusable.
Finally, you load or reload the page with the focus set on one item.
HTML below

<div oc-lazy-load="['/static/app1.js']">
<style>
g:focus {
  outline: none !important;
}
</style>
<body ng-app="App">

<svg width="1500px" height="500px"  xmlns="http://www.w3.org/2000/svg">
<g  next-focus=""  auto-focus="" id="recta0" >
<rect  x="10" y="20" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta0.focusin" end="recta0.focusout" to="red"/>
 <set attributeName="width" begin="recta0.focusin" end="recta0.focusout" to="200"/>
</rect>
<text x="42" y="58" fill="white"  font-weight="20" font-weight=normal >Raspberry Pi
<set attributeName="font-size" begin="recta0.focusin" end="recta0.focusout" to="25"/>
<set attributeName="font-weight" begin="recta0.focusin" end="recta0.focusout" to="bold"/>
</text>
</g>

<g  next-focus id="recta1" >
<rect  x="250" y="20" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta1.focusin" end="recta1.focusout" to="red"/>
 <set attributeName="width" begin="recta1.focusin" end="recta1.focusout" to="200"/>
</rect>
<text x="280" y="58" fill="white"  font-weight="20" font-weight=normal >Strawberry Pi
<set attributeName="font-size" begin="recta1.focusin" end="recta1.focusout" to="25"/>
<set attributeName="font-weight" begin="recta1.focusin" end="recta1.focusout" to="bold"/>
</text>
</g>


<g  next-focus="" id="recta2" >
<rect   x="490" y="20" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta2.focusin" end="recta2.focusout" to="red"/>
 <set attributeName="width" begin="recta2.focusin" end="recta2.focusout" to="200"/>
</rect>
<text x="532" y="58" fill="white"  font-weight="20" font-weight=normal >Blueberry Pi
<set attributeName="font-size" begin="recta2.focusin" end="recta2.focusout" to="25"/>
<set attributeName="font-weight" begin="recta2.focusin" end="recta2.focusout" to="bold"/>
</text>
</g>

<g  next-focus="" id="recta3" >
<rect   x="10" y="120" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta3.focusin" end="recta3.focusout" to="red"/>
 <set attributeName="width" begin="recta3.focusin" end="recta3.focusout" to="200"/>
</rect>
<text x="42" y="152" fill="white"  font-weight="20" font-weight=normal >Cranberry Pi
<set attributeName="font-size" begin="recta3.focusin" end="recta3.focusout" to="25"/>
<set attributeName="font-weight" begin="recta3.focusin" end="recta3.focusout" to="bold"/>
</text>
</g>

<g  next-focus="" id="recta4" >
<rect   x="250" y="120" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta4.focusin" end="recta4.focusout" to="red"/>
 <set attributeName="width" begin="recta4.focusin" end="recta4.focusout" to="200"/>
</rect>
<text x="280" y="152" fill="white"  font-weight="20" font-weight=normal >Blackberry Pi
<set attributeName="font-size" begin="recta4.focusin" end="recta4.focusout" to="25"/>
<set attributeName="font-weight" begin="recta4.focusin" end="recta4.focusout" to="bold"/>
</text>
</g>


<g  next-focus="" id="recta5" >
<rect   x="490" y="120" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta5.focusin" end="recta5.focusout" to="red"/>
 <set attributeName="width" begin="recta5.focusin" end="recta5.focusout" to="200"/>
</rect>
<text x="512" y="152" fill="white"  font-weight="20" font-weight=normal >Huckleberry Pi
<set attributeName="font-size" begin="recta5.focusin" end="recta5.focusout" to="25"/>
<set attributeName="font-weight" begin="recta5.focusin" end="recta5.focusout" to="bold"/>
</text>
</g>


<g  next-focus="" id="recta6" >
<rect   x="10" y="220" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta6.focusin" end="recta6.focusout" to="red"/>
 <set attributeName="width" begin="recta6.focusin" end="recta6.focusout" to="200"/>
</rect>
<text x="35" y="252" fill="white"  font-weight="20" font-weight=normal >Gooseberry Pi
<set attributeName="font-size" begin="recta6.focusin" end="recta6.focusout" to="25"/>
<set attributeName="font-weight" begin="recta6.focusin" end="recta6.focusout" to="bold"/>
</text>
</g>


<g  next-focus="" id="recta7" >
<rect   x="250" y="220" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta7.focusin" end="recta7.focusout" to="red"/>
 <set attributeName="width" begin="recta7.focusin" end="recta7.focusout" to="200"/>
</rect>
<text x="290" y="252" fill="white"  font-weight="20" font-weight=normal >Mulberry Pi
<set attributeName="font-size" begin="recta7.focusin" end="recta7.focusout" to="25"/>
<set attributeName="font-weight" begin="recta7.focusin" end="recta7.focusout" to="bold"/>
</text>
</g>


<g  next-focus="" id="recta8" >
<rect   x="490" y="220" fill="blue"  width="190" height="60"   >
 <set attributeName="fill" begin="recta8.focusin" end="recta8.focusout" to="red"/>
 <set attributeName="width" begin="recta8.focusin" end="recta8.focusout" to="200"/>
</rect>
<text x="522" y="252" fill="white"  font-weight="20" font-weight=normal >Pineberry pi
<set attributeName="font-size" begin="recta8.focusin" end="recta8.focusout" to="25"/>
<set attributeName="font-weight" begin="recta8.focusin" end="recta8.focusout" to="bold"/>
</text>
</g>

<g  next-focus="" id="recta9" >
<rect   x="10" y="320" fill="blue"  width="130" height="40"   >
 <set attributeName="fill" begin="recta9.focusin" end="recta9.focusout" to="red"/>
 <set attributeName="width" begin="recta9.focusin" end="recta9.focusout" to="150"/>
</rect>
<text x="30" y="345" fill="white"  font-weight="20" font-weight=normal >Next Page
<set attributeName="font-size" begin="recta9.focusin" end="recta9.focusout" to="25"/>
<set attributeName="font-weight" begin="recta9.focusin" end="recta9.focusout" to="bold"/>
</text>
</g>

</svg>

</body>
</div>

app1.js

var App = angular.module('App', []);
App.directive("nextFocus", nextFocus);
    function nextFocus() {
      var directive = {
        restrict: 'A',
        link: function($scope, elem, attrs) {       
          elem.bind('keydown', function(e) {
            var partsId = attrs.id.match(/recta(\d{1})/);
            var currentId = parseInt(partsId[1]);

            var code = e.keyCode || e.which;
            if (code === 39) {
              e.preventDefault();
              document.querySelector('#recta' + (currentId + 1)).focus();
            }
            
            if (code === 37) {
              e.preventDefault();
              document.querySelector('#recta' + (currentId - 1)).focus();
            }
            
            if (code === 40) {
              e.preventDefault();
              document.querySelector('#recta' + (currentId + 3)).focus();
            }
            
            if (code === 38) {
              e.preventDefault();
              document.querySelector('#recta' + (currentId - 3)).focus();
            }
            
          });
        }
      };
      return directive;

    }  
 App.directive('autoFocus', function($timeout) {
    return {
        restrict: 'AC',
        link: function(_scope, _element) {
            $timeout(function(){
                _element[0].focus();
            }, 1);
        }
    };
});

I guess someone can improve on the code by minimizing it using style.

Great post, all working as expected.

One (lame) question: how to force openhabian (raspberry) to re-read floorplan/svg/css uploaded to HTML directory? I need to change filename each time and edit template widget settings to new filename. There must be some sort of cache in built in web server, but not sure where to force update.
Thanks in advance!

Try a “force reload” on the browser side. I have the same issue using Safari/macOS. A simple refresh doesn’t help to reload a new floorplan (embedded in HABpanel) uploaded to html. Instead, I have to click Safari ‣ Develop ‣ Clear Caches and reload afterwards. There will be a similar function for other browsers.

1 Like

Thanks. Forced reload worked without need to clear cache (Firefox)

I tried this with Number:Temperature, but it does not work:
My item:

Number:Temperature               CarTemp                "Temperatur am Auto [%.1f °C]"                 <temperature>    (G_Num,gOutdoorCar)                ["Measurement", "Temperature"]    {widgetOrder="03"}

But I get:

image

With this svg code:
ng-bind="itemState('CarTemp')"

I also tried
[%.1f %unit%]

And various formating stuff besides your suggestions above:

ng-bind={{itemValue('CarTemp') | number:1}}

ng-bind={{'%.1f' | sprintf:itemValue('CarTemp').split(' ')[0]}} {{itemValue('CarTemp').split(' ')[1]}}

Any suggestion would be greatly appreciated.

@ysc is this also working for openhab 3 or is there a better solution now?

HABPanel is still in OH3 so yes, it’s still working. The main UI has floorplan pages but they are not the same and arguably not as powerful.

Has anybody an idea, why the following usage of ng-class does perfectly working on an older iPad (iOS 12.5) in Safari browser and do not show any effect on a new MacOS 12.2. or on Windows10? … not with Safari and not with FireFox.

  <circle
     spot-ez1="circle9604"
     style="opacity:0.95;fill:url(#radialGradient9730);fill-opacity:1;stroke:#ff9f31;stroke-width:0.185;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
     id="spot-gz1"
     cx="80.726265"
     cy="24.550516"
     r="3"
     ng-class="{&quot;spots-on&quot;: itemState('EG_GZ_Spots_Schalten',true) == 'ON' }"
     ng-click="sendCmd('EG_GZ_Spots_Schalten', (itemState('EG_GZ_Spots_Schalten',true) == 'ON') ? 'OFF' : 'ON')" />

// CSS file
.spots-on {		
	r:"15" !important;		
}

BTW: the ng-click statement is being executed in all environments!

All, I found the solution. New browser versions do not like a

r=“15”

within a CSS file. I changed it to

r=15px

and now it’s working. In a SVG object you can do both.

This sounds really similar to my own creation. I have used inkscape to create a multi-layer SVG that I use to control all my smart devices.
Here are some pictures of my “Whole House Remote”'s layers.
Main Floor:


Basement:

Backyard:

Frontyard:

Utility Details:

I have added animations to many items, the ceiling fans rotate when the switch is on, and are stationary when off. Everything is sensitive to touch/click, clicking on the items or fans will send the proper command to the items to make the remote really work like a remote, not just a viewer. Lights obviously change color based on state. The energy meter updates every second, and the pointer on the meter moves to the correct location. Temperature meters change color based on the temperature (blue for cold, red for hot etc…). You can see the color options for temperature within the thermostat selector on the right. The motion Sensors (running man icon) change colors based on last detected motion time. example (If the motion sensor was just activated the color will be red, and after 5 min the color will be black. The color will change based on the gradient pattern displayed on the top left of the main floor layer. I really like this feature as it allows me easily at a glance not only know where the motion is and how long ago it was. It is quite easy to see the path of someone that moves through the house based on the color. I have also integrated some light sensors and I display them with a black background for dark, and light grey background for bright and then vary the level of grey based on the actual value of the sensor (pretty sure I once again just used the alpha channel to produce the varying grey color). I have also been able to get the Circular part of the thermostat to slide up and down (only vertical movement) with touch/clicking&dragging to set the Set Point of my nest to the desired temperature.

Clicking/Touching the SVG on the stairs will switch to the basement view by simply hiding the layer for the main floor and displaying the layer for the basement. In my case I simply change the alpha channel from 100 to 0 (to make invisible) and opposite to make it visible. This gives me the ability to traverse the floor plan in a continuous way, that just feels more natural. then to return to the main floor just click the stairs on the layer for the basement to get back to the main floor.

It is also possible to click/touch the back deck to switch to the backyard view, and then click on the house to get back to main floor. Similar for the front yard.

selecting one of the images at the bottom of the page will display a full size version of the video feed for an easier time actually seeing details in the video.

clicking/touching the Energy Meter will bring up the energy details item that will display the calculated costs of electricity broken down into time periods for comparison and gives me the ability to detect abnormal usage conditions quickly. I display the amp’s used inside the meter itself.

The Irrigation details are still a work in progress. I just recently (Finally) got the Rachio web hook working and now receive updates to my sprinkler items. So now I can (next time I pick up this project again) add details and animations for the irrigation system.

I am using AngularJS as well to make it all work together.

This being an SVG it looks good on all displays, even my iPhone display looks nice. being AngularJS under the hood however does limit iOS devices to versions that have desktop class web browsers (I think its like iOS 13 or newer) but as time goes by that problem is going away.

TODO:
I still have aspirations to intergrate Some sort of water flow meter to get actual water usage numbers (Price is prohibitive still). Think I found a way to grab the data from my gas meter directly the same way my gas company does.
I have also created a custom website to help my children with their chores. I get tired of having to remember everything so this way a website can do it for me. I am seriously considering integrating that into OpenHab in hopes I could then use Siri to interact with my kids chore lists, mark chores as complete, add new chores to their lists etc… via the HomeKit add-on. For some reason I think my kids would listen to Siri more then me, so being able to ask siri what chores are left to be completed for the day and it verbalizing the list announcing over my HomePod would probably get better results then me telling, and telling, and telling, and yelling and … Not to mention the “Cool” factor might help them continue to use it for more then a week.

1 Like

you can do gas and water meter read like this .

How do you get the fans to animate?
I used to have that option on the home automation I used to use before switching to openhab.

Thanks

I use CSS on mine, when the switch is enabled I add a class to the fan that causes it to rotate, and when the switch is turned off I remove that class. Of course you have to make your SVG an inline SVG to be able to apply CSS transforms to it easily. If you don’t have your SVG inline then the SVG lives in its own coordinate system and CSS dosen’t have the ability to modify components within the SVG, like the fans, or if it does it tries to use its coordinate system to calculate any transformations and apply it to a completely different coordinate system, which results in bad things. I also give each fan’s group in Inkscape a unique ID so it is easy to get ahold of the item in AngularJS to apply the transformations/class.

Incase your unsure what an inline SVG is, Basically you open the SVG file inkscape creates, copy the entire SVG from <svg> to </svg> and paste it directly into your HTML file in the location you want it to load inside the page. It’s a bit of a pain (also the code looks very messy/ugly) if you’re still building it, and of course you will need to empty the caches and reload the webpage after most updates to get the changes to appear. but having the ability to use CSS on individual pieces of the SVG in my opinion is worth the extra hassle.

That’s awesome stuff - thank you!

1 Like

What do you mean?
Will it be not supported in the future anymore?