Uibuilder + VueJS + SVG = visual IoT floorplan

Hi all, just a quick post to share something I've been messing with for the last couple of days. Posted the flow to the flows site because it is too long for Discourse. That flow is very likely to get updated as it gets improved.

Looks like I'll also be publishing a package of VueJS components at some point as well which will be designed to work seamlessly with uibuilder though will likely work standalone as well.

This one was inspired by Steve and Bart's SVG addon for Dashboard. It made me wonder how hard it would be to combine an SVG floorplan with SVG icons showing things like whether lights are on or off and even being able to control them.

Well, turns out that it is crazy simple!

So I created a simple VueJS component which means that all you have to do is write a couple of lines of HTML & a few lines of JavaScript then you have a fully dynamic visual floorplan with IoT overlays.


In this case, just the 1 light is turned on, notice that it is a different colour and glowing :sunglasses:

The flow to drive this is also trivial:
image

It is too long to post here because of the embedded SVG data so I've created an entry on the flows site. Note that the html and js files seem a bit long as well but that is only because the component is embedded, normally that would be in a separate file.

https://flows.nodered.org/flow/02eb3716157db586f3f5b8a85c241009#

The flow contains the html and javascript as well as the SVG floorplan used for the background. Just copy the content of the SVG to a file called background.svg then copy/paste the html and javascript to the appropriate files.

Here is the relevant part of the HTML:

        <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(./images/serveimage.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100" y="100" @bulbclicked="myClick" title="A: This one does not respond to clicks"><desc>Here we use a component slot to insert some more custom svg</desc></bulb>
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true"  x="270" y="120" @bulbclicked="myClick" title="B"><circle cx="50" cy="50" r="50"/></bulb>
            <bulb id="c" :ison="isonc" :clickable="true"  x="650" y="120" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true"  x="250" y="270" @bulbclicked="myClick" :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>
        </div>

I will be simplifying that still further I think, really the last 2 of the 4 examples are the style you would mainly want to use, the first 2 show just how versatile a component can be.

Here is the relevant part of the JavaScript:

var app1 = new Vue({
    el: '#app',
    data: {

        isonb: false,
        isona: false,
        isonc: false,
        isond: false,

    }, // --- End of data --- //
    
    methods: {

        // Called if one of the bulb icons is clicked
        myClick: function(returnedData) {

            // returnedData.srcId contains the element id, returnedData.event contains the click event object
            console.log('Somebody clicked the ' + returnedData.srcId + ' icon!', returnedData)

            var out

            if ( returnedData.srcId === 'a' ) {
                out = this.isona = !this.isona
            }
            if ( returnedData.srcId === 'b' ) {
                out = this.isonb = !this.isonb
            }
            if ( returnedData.srcId === 'c' ) {
                out = this.isonc = !this.isonc
            }
            if ( returnedData.srcId === 'd' ) {
                out = this.isond = !this.isond
            }

            // Lets tell Node-RED
            uibuilder.send({
                'topic': returnedData.srcId.toUpperCase(),
                'payload': out,
            })

        }, // -- End of myClick -- //

    }, // --- End of methods --- //

    // Get things started
    mounted: function(){

        /** **REQUIRED** Start uibuilder comms with Node-RED */
        uibuilder.start()

        // msg is updated when a standard msg is received from Node-RED
        uibuilder.onChange('msg', function(msg){

            if ( msg.payload.hasOwnProperty('A') ) {
                app1.isona = msg.payload.A
            }
            if ( msg.payload.hasOwnProperty('B') ) {
                app1.isonb = msg.payload.B
            }
            if ( msg.payload.hasOwnProperty('C') ) {
                app1.isonc = msg.payload.C
            }
            if ( msg.payload.hasOwnProperty('D') ) {
                app1.isond = msg.payload.D
            }

            console.log('incoming msg', app1.isona, app1.isonb, app1.isonc, app1.isond, msg)
        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

You may be able to see how this could be improved with further automation so that you don't have to specify the bulb entries manually but instead can get the data from Node-RED and let VueJS build the icons for you.

9 Likes

Of course if we could make any data formats as common as possible across various widgets across both dashboards that would be fantastic.

OK I know that's not quite how ui-builder works right now - but if you are heading towards a component library (and why not) - then if there was a way to make migration easier then why not.

1 Like

Hi Julian,
That looks like a floorplan of a castle - you lucky devil.
I noted you only showed us the 'west wing'.

4 Likes

Hi Dave, totally agree!

Indeed, I believe that standard schema's are the best approach. Question is, how do we encourage people to publish them and how do we document them? :smiley:

Haha, wondered if someone would spot that - rest assured that it isn't my house.

Thought it might be amusing though.

:smiley:

I'm totally gutted as I thought it was your 'pad'.

Being serious for a moment, I can see the combination of the aforementioned pieces of software will provide a spring-board for many creative minds on this forum. Thanks for sharing.

1 Like

Well, I've built some vue components (separate vue files) like:
switch/button, slider, text ,chart (based on ApexCharts).

It works quite well for me, although it's still a work in progress.

The idea is to publish them after a testing period.

That's great to know. But actually, what is more important is how do we collectively seek to publish standardised data schema's?

What data to we need to send to a button component, a slider component, a chart component? In what format?

We clearly need a unique identifier for each instance of each component for example so we need a standard for that.

For buttons, lists, text, etc, do we allow sending of colours or do we require sending of a css object?

How do we deal with msg.topic? Use of msg.payload for data seems like a no-brainer but what about formatting info - msg.format? msg.css?

Do we have a standard for emptying the content of suitable components - for example, removing the data from a chart?

How and where would we document all of this assuming we collectively agreed to do it?

Food for thought.

Some initial thoughts. This is the kind of thing that I've been thinking about for uibuilder anyway.

var msg = {
    // Target component instance identifier (must be unique)
    "id": {String},           
    /** CSS Class names and/or CSS style formatting for target component instance
     * It is up to the component to decide how to use this info */
    "format": {Object},          
    /** Configuration data for the target component instance
     * Property name chosen to be common with Dashboard */
    "ui_control": {Object},
    // Data for the component - component specific formats, to be decided
    "payload": {Any},
    // Secondary identifier - component specific use    
    "topic": {String},
    // Node-RED message identifier
    "_msgid": {String},

    /** Not sure about these 
     * Also, might be better to include them in the ui_control property object
     */

    /** Target component type
     * Would allow self-creating interfaces.
     * First time sending a msg with specific id and component type
     * would automatically add that to the UI.
     */
    "component_type": {String},

    /** If using the above, would need these too */
    "parent": {String},  // CSS identifier of the parent this would be attached to
    "position": {Number|'first'|'last'}, // Could be -1 for last and 0 for first
}

I'd assume we'd take the messages used by Node-RED Dashboard as the starting point and then discuss any modifications from there. If the point is to help migration/portability between different dashboard nodes, we may as well start with the most well-established.

2 Likes

The current ui nodes use msg.ui_control to allow access to most underlying configuration - see https://github.com/node-red/node-red-dashboard/blob/master/config-fields.md - so that could be your config.

msg.topic is fairly safe though is used to identify different lines in a ui_chart so should not be imbued with any extra special significance.

id and css are "unique" to ui_builder I think - (as dashboard has one editor node per ui widget so doesn't need id and css is set by the theme (unless overridden by a ui_template node). Is the css here just overrides per widget ? or more global than that ? Just wondering if they should be combined into a single property that holds both id and css and thus room for anything else we have yet to remember.

Seems sensible if they fit. I wasn't aware that they were documented anywhere which was more the issue. Not sure that I have much time to hunt through everything. But as Dave has pointed out a link, that seems like a decent starting point.

Each Dashboard node still uses its own payload format though and I don't believe there is a single resource documenting that. I will try to set aside some time when I build new components to see if there is anything reasonably similar in Dashboard and whether it supports the data needed.

Those seem useful starting points for the config indeed and I see no issue with adopting ui_control instead of config. I'll update the entry.

You are probably correct right now, who knows about the future.

ID seems pretty obvious as a starting point since you really do need something to anchor the back-end to the front-end. But of course, Dashboard probably doesn't currently need to worry about that since each front-end component has its own matching back-end node.

Which is great when you just need a single theming for all components. I think that uibuilder might need more flexibility in some cases. Where it doesn't, you can, of course, simply have a CSS file so we don't need to do a theming node. I would probably extend the css object to include a class string which would be sensible.

The idea - and do bear in mind that these are just ideas - is that you can specify CSS a lot easier as an object. Vue already handles this and so you can pass the object direct to a component & let Vue munge it together into the actual CSS. I think that a lot of other frameworks have similar capabilities.

Here is somewhere that needs some balance I believe. A single component (widget) could actually comprise of multiple html tags and the component would need to take care of how it handled the incoming CSS specification. It could even end up split between multiple tags. Indeed, that kind of already happens on the svg example since the colour needs to be passed to the <path> tag not the parent <svg> tag.

It comes down to good design at the component level. For example, the svg example allows you to pass custom svg elements in so if you need the ultimate control, you can do that. The generic input data should, I think, handle the common scenarios. Anything really custom should be catered for by custom coding in the front-end.

Well, it is a thought. I kind of deliberately kept the id separate, the general formatting together (called CSS but maybe that is the wrong naming) and the detailed component configuration together. Then with the payload for the main data, that seemed like a decent set of things to use while still allowing for considerable configuration without making the msg object too heavy. Pretty much everything except the payload and maybe the id would be optional anyway.


This is all speculation at the moment so it is great to have the discussion. If we can find any other areas of common data, that will be fantastic.

Hmm, as I've been thinking about these. Perhaps it would be better to roll them all into the ui_control property?

1 Like

Hi Julian
Nice Idea, I've had a go ... loads background but displays light bulbs all in the top-left corner on top of each other. They do turn on and off, and i get the tool-tip for 'D'. I changed the name 'background.svg' to match index.html 'serveimage.svg'.
any suggestions?

Hi, thanks.

All you need to do is to pass x and y values to the bulb entries. You can use px or %.

If you've done that and they are still in the same place, it means that you haven't made the parent (the one with the background probably) position:relative. If you don't do that, the position:absolute of the bulbs is measured against the browser window instead of the background element.

A proper version would fix that for you. At some point I will no doubt get time to do that.

Julian, for future improvements have you thought of creating components and utilising the <slots> aspect to add things like the elements inside? That way you can easily hook them together but hide some of the background logic away.

1 Like

Hi Lena, yes absolutely, I've already built some slots into the example and it is incredibly powerful.

The example uses a slot to add extra content - in the first case just adding a <desc> and in the second, adding a circle (its a bit small in the example and appears as a dot but hopefully shows the possibilities).

Slots are hard to get your head round to begin with but are an incredibly powerful feature of Vue components.

Hey TotallyInformation (and other), this is really cool. I'm a newbie at NodeRed (I've been doing embedded programming for decades, but I'm pretty new to NodeRed) so I'm just going to lurk and keep an eye on this. I'm posting this message just to encourage you in your endeavors and to say that it looks really nice.

On a possibly related topic, have any of you used Vixen Lights? It's something that you can use to create those big light shows for your house (or your castle or your West Wing) at Halloween, Christmas, etc. They have an option where you can import a picture of your house and overlay the image with light strands and such so you can get an idea of what your house would look during the show. I thought I'd bring this up because I didn't know if this was something you guys could leverage off of, get some ideas from, or whatnot...

Anyway you slice it, great work so far, and I look forward to seeing how this evolves! :slight_smile:

1 Like

Hi Jon, thanks for the encouragement! :smiley:

No, I've not looked at those and honestly, Mrs TI wouldn't stand for them :rofl:

But that may be a useful comment to others so thanks for that.

I can see that it would be pretty easy to replace VixenLights with Node-RED, uibuilder (or indeed Dashboard/node-red-contrib-svg) and some convenient ESP8266's.

Indeed, it might be interesting to have some permanent LED strings around then create appropriate SVG background images - even from photos. Place sets of "bulb" type components and send data from Node-RED to both the ui and to the ESP's.

Some thoughts for anyone who might want to try:

  • Use the power of Vue to create a component that contains multiple "bulb" components along a straight line. Letting you define just the start and end positions and number of lights. Get really clever by allowing some "droop" (will need some math :sunglasses:)
  • Add a blocking "switch" to your flow so that you can send the output just to the ui when you want to mess with it. Then unblock it to send it to the devices.
1 Like

Hi,
many thanks for this inspiring flow, exactly what I have been looking for. Although I made some first experiences with Node-red I am facing some difficulties now. I have imported the flow as described...copied the content to dedicated flies, but there it stops...I do not know where do store index.html, index.js and background.svg and how to invoke the flow...can you please give a noobie some advice? Many many thanks for that in advance
Kind regards,
Michael