Floorplan-Creator icon indicating copy to clipboard operation
Floorplan-Creator copied to clipboard

Overlay on picture elements floorplan [FEATURE]

Open ihrapsa opened this issue 2 years ago • 4 comments

Hi, I really love this concept. I wonder if this could be overlayed on a Hone Assistant picture element floorplan. I haven't found any information about that but I think it would be a nice feature. I'm not sure about the technicalities that would allow that or not. What do you think about this? :)

Maybe I should ask this in the ad-espresense-ips repo?

Thanks!

ihrapsa avatar Apr 16 '22 00:04 ihrapsa

Hi, I've been fiddling with some js and css (even before making this app). I did manage to get the devices to display in a plan but the major problem was perspective. I have been wondering myself if the floorplan could be added to home assistant as a UI page for ESPresenceIPS. It could be a nice start.

Answering your question, technically speaking it is possible. Maybe not with this app, you might need to implement it yourself on your floorplan page.

What I did was : Draw some divs in absolute position for each room. Position them to map your home. Add a trigger for your devices position change in floorplan config. (For an easy test, use 1px = 1cm). Now when your trigger triggers, create an absolute div for that device (if not exist) and set its top and left css to be x and y. It should now move over your plan. Next is fiddling with css matrix3D to get the perspective.

I can try to find this code and send it to you if you want (it did work but the perspective was not achieved) so you have a base to work on.

It would be nice to have it in ESPresenceIPS but it will require an amount of time i'm not sure I can invest right now.

stan69b avatar Apr 16 '22 06:04 stan69b

Wow, thanks a lot for the thorough response! I've been looking a bit online and found some nice ips examples but with robot vacuums. Maybe we can take inspiration from there. I found this unbelievably well done lovelace card: Lovelace-Xiaomi-Vacuum-Map-card but this is not using the picture elements approach as far as I can tell.

Also, this video made me think that we could use a script to change the style: "top: %; left: %;" of an elment based on the ESPresense coordinates (translated to the floorplan picture coordinates). Is this easier said than done?

I'm looking forward to your experiment code when you have time.

ihrapsa avatar Apr 16 '22 12:04 ihrapsa

Hey, I'm so sorry, I just completely forgot and did not receive a notification when you responded.

first I created sensors with the json payload as state (in configuration.yaml) : something llike this should do it.

  - platform: mqtt
    name: Macbook Pro IPS
    state_topic: "espresense/ips/apple:XXXX:Y"

in my floorplan.yaml config, I added this :

- entities:
          - sensor.macbook_pro_ips
          - sensor.unknow_apple_device_ips
          - sensor.apple_1
          - sensor.apple_2
          - sensor.uknown_device
          - sensor.unknown_device_2
        tap_action:
          action: none
        state_action:
          action: call-service
          service: floorplan.style_set
          service_data:
            style: |
              >
              console.log(entity);
              console.log(entity.state);
              console.log(element);
              if (entity && entity.state && entity.state != 'unknown' && element) {
                var data = JSON.parse(entity.state);
                var xFinal = data.x*100;
                var yFinal = data.y*100
                // element.title = entity.id;
                element.style.transform = 'translate('+xFinal+'px,'+yFinal+'px)';
              }
              console.log(element)
              return "";

this code will watch certain devices from. IPS and trigger a js code on state change.

than add : to your floorlpal.svg file (before closing tag) these elements are the ones automatically targeted by the above js code (in yaml config) to set the positions.

<g id="home_ips">
   <rect id="kitchen_ips" width="344" height="423" fill="green" />

<!-- example/test positions -->
   <rect id="livingRoom_ips" style="transform: translate(354px, 138px)" width="397" height="659" fill="white" />
   <rect id="office_ips" style="transform: translate(751px, 0px)" width="289" height="485" fill="yellow" />
   <rect id="bedroom_ips" style="transform: translate(363px, 797px)" width="388" height="403" fill="purple" />
<!-- example/test  -->

   <g id="sensor.macbook_pro_ips">
      <circle fill="blue" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.unknow_apple_device_ips">
      <circle fill="red" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.apple_1">
      <circle fill="pink" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.apple_2">
      <circle fill="grey" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.uknown_device">
      <circle fill="black" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.uknown_device">
      <circle fill="black" cx="50" cy="50" r="50"/>
   </g>
   <g id="sensor.unknown_device_2">
      <circle fill="orange" cx="50" cy="50" r="50"/>
   </g>
</g>

now you should have discs moving on your page based on the locations of the devices. keep in min this code is 1px = 1cm i think. and it will start from top left of your svg frame. I would advice to set the circle container to absolute, ether scale or change the px to m ratio to fill your plan. If the plan if in perspective (mine is) you will have to play with matrix3d CSS rule to make it fit your perspective.

have fun with all this. It is going to be challenging to get it perfectly as you want. I will gladly assist you in this if you plan on trying out the code et fixing it to your needs. If the result you/we come up with seems to work fine, I might see if we can make something cleaner out of it if needed :)

stan69b avatar Apr 25 '22 07:04 stan69b

"example/test" comment in the svg code is actually the ips bases (esp32) locations. I used it for testing, they are not needed but great to check your accuracy.

css "transform" can be changed to "transform3d" to use GPU acceleration in the browser :) and you can add a transition: transform .25s ease-in-out; or other timing/easing to have nice animations between position if the element jumps too much.

for css overides and js custom script, I added a custom js file to my dashboards that removes the top bar on the floorplan : (this is my custom js file) you have an exagone as well, i am playing with buttons in the corners, but i added them in the svg now.

console.log("Custom js file loaded !");
const exagone = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="52" height="60" viewbox="0 0 51.96152422706631 60" style="filter: drop-shadow(rgba(255, 255, 255, 0.5) 0px 0px 10px);"><path fill="#fff" d="M25.980762113533157 0L51.96152422706631 15L51.96152422706631 45L25.980762113533157 60L0 45L0 15Z"></path></svg>';
var element = document.querySelector("floorplan-card");
var homeAssistant, homeAssistantMain, appDrawerLayout, haPanelLovelace, huiRoot, haAppLayout,
    huiPanelView, huiPictureEelementsCard, haCard, element, floorplanElement, buttonContainer;
var interval;

window.addEventListener('popstate', function (event) {
	console.log("CEFSDX", event);
});

window.addEventListener('locationchange', function(){
    console.log('onlocationchange event occurred!');
})

window.addEventListener('load', (event) => {
    console.log('page is fully loaded');
});

init();

function init() {
    setTimeout(function() {
        interval = setInterval(function() {
            homeAssistant = document.querySelector("home-assistant");
            homeAssistantMain = homeAssistant?.shadowRoot?.querySelector("home-assistant-main");
            appDrawerLayout = homeAssistantMain?.shadowRoot?.querySelector("app-drawer-layout");
            haPanelLovelace = appDrawerLayout?.querySelector("ha-panel-lovelace");
            huiRoot = haPanelLovelace?.shadowRoot?.querySelector("hui-root");
            haAppLayout = huiRoot?.shadowRoot?.querySelector("ha-app-layout");
            
            huiPanelView = haAppLayout?.querySelector('hui-panel-view');
            huiPictureEelementsCard = huiPanelView?.shadowRoot?.querySelector('hui-picture-elements-card');
            haCard = huiPictureEelementsCard?.shadowRoot?.querySelector("ha-card");
            
            element = haCard?.querySelector("floorplan-card");
            var tmp = element?.shadowRoot?.querySelector("ha-card");
            floorplanElement = tmp?.querySelector("floorplan-element");
            
            buttonContainer = document.createElement("div");
            buttonContainer.classList.add("buttonContainer");
    
            var root = haCard?.querySelector("#root");
    
            if(element && floorplanElement && root) {
                element.style.display = "block";
                element.style.transform = "translate(0)";
                element.style.maxHeight = "100%";
                element.style.justifyContent = "center";
                element.style.alignItems = "center";
                clearInterval(interval);
                var headerCss = `
                    app-header {
                        display: none !important;
                    }
    
                    #view {
                        min-height: calc(100vh) !important;
                    }
                `
    
                var shadowCss = `
                    #contentContainer {
                        padding: 0 !important;
                    }
                `
                    
                var contCss = `
                    hui-picture-elements-card {
                        padding: 10px !important;
                    }
                `
                addcss(contCss, huiPanelView.shadowRoot);
                addcss(headerCss, haAppLayout);
                addcss(shadowCss, haAppLayout.shadowRoot);
            }
        }, 100);
    }, 100);
    
    setTimeout(function() {
        clearInterval(interval);
    }, 5000);
}

function addcss(css, container) {
    var s = document.createElement('style');
    s.classList.add("floorplan-custom-css");
    s.setAttribute('type', 'text/css');
    if (s.styleSheet) {   // IE
        s.styleSheet.cssText = css;
    } else {                // the world
        s.appendChild(document.createTextNode(css));
    }
    
    container.appendChild(s);
}

stan69b avatar Apr 25 '22 07:04 stan69b