Dashboard 2.0 ui-template multiple injections

Hi.

I have a use case where I ask devices out in the field (PLS and the like) over different protocols, like Modbus, Omron FINS etc. This data I get back and I process that data and presents the data to a user through ui-template. But sometimes there might be a malfunction in the response back from devices. This breakes the data flow. I would like to notify the user that there might be some communication error occurring.
Now I inform the user with a time and date when the template is updated.
But I would also like to notify the user with a notification that there is one minute since last update, some error is occurring.

The template node gets its data through msg.data.

This I have tried:
I would like to update the template node with msg.time individually outside the flow that asks devices for data. That way I could give a notification to a user when a time limit is passed between data recieved. But at the same time I want the template to not update the data ui. I want the template to persist the last data and present it. But when you send the template node msg.time but not msg.data it crashes.

I'm thinking that maybe the easiest way is after the response from the device I could store the msg.data in a function nodes context scope. And outside the flow that requests for data I could go through that function node and use the last saved data in the context scope?

Or is there a way to not crach the template when it doesn't get the msg.data...?

Best regards
Steffen

Show us what a good message looks like and a bad message, by using a Debug node set to Output Complete Message.

You can easily achieve this using something like topic to control what gets updated.

e.g.

chrome_gLU63BEcvO

<template>
    <div>
        <div>
            <p :style="{'color' : (count > 5 ? 'red' : 'green' )}">Current Count: {{ count }}</p>
            <p :style="{'status' : (status == 'fault' ? 'red' : 'black' )}">Current Status: {{ status }}</p>
            <v-btn @click="resetCounter">Reset Counter</v-btn>
        </div>
        <div v-show="lastError" >
            <p style="color: red">Last Error: {{ lastError }} @ {{lastErrorTime}}</p>
            <v-btn type="danger" @click="resetError">Reset Error</v-btn>
        </div>

    </div>

</template>

<script>
    export default {
        data() {
            // define variables available component-wide
            // (in <template> and component functions)
            return {
                count: 0,
                status: "init",
                lastError: null,
                lastErrorTime: null,
            }
        },
        watch: {
            msg: function () {
                if (this.msg.topic === 'counter') {
                    this.count = this.msg.payload
                } else if (this.msg.topic === 'status') {
                    this.status = this.msg.payload
                } else if (this.msg.topic === 'error') {
                    this.lastError = this.msg.payload
                    this.lastErrorTime = (new Date()).toISOString()
                }
            }
        },
        methods: {
            resetCounter() {
                this.count = 0
            },
            resetError() {
                this.lastError = null
            }
        }
    }
</script>

demo flow:

[{"id":"7d71bd185726b4cc","type":"ui-template","z":"862ec766f2af6f61","group":"b1da20a2d7d13012","page":"","ui":"","name":"","order":0,"width":"9","height":"10","head":"","format":"<template>\n    <div>\n        <div>\n            <p :style=\"{'color' : (count > 5 ? 'red' : 'green' )}\">Current Count: {{ count }}</p>\n            <p :style=\"{'status' : (status == 'fault' ? 'red' : 'black' )}\">Current Status: {{ status }}</p>\n            <v-btn @click=\"resetCounter\">Reset Counter</v-btn>\n        </div>\n        <div v-show=\"lastError\" >\n            <p style=\"color: red\">Last Error: {{ lastError }} @ {{lastErrorTime}}</p>\n            <v-btn type=\"danger\" @click=\"resetError\">Reset Error</v-btn>\n        </div>\n\n    </div>\n\n</template>\n\n<script>\n    export default {\n        data() {\n            // define variables available component-wide\n            // (in <template> and component functions)\n            return {\n                count: 0,\n                status: \"init\",\n                lastError: null,\n                lastErrorTime: null,\n            }\n        },\n        watch: {\n            // watch for any changes of \"count\"\n            msg: function () {\n                if (this.msg.topic === 'counter') {\n                    this.count = this.msg.payload\n                } else if (this.msg.topic === 'status') {\n                    this.status = this.msg.payload\n                } else if (this.msg.topic === 'error') {\n                    this.lastError = this.msg.payload\n                    this.lastErrorTime = (new Date()).toISOString()\n                }\n            }\n        },\n        methods: {\n            resetCounter() {\n                this.count = 0\n            },\n            resetError() {\n                this.lastError = null\n            }\n        }\n    }\n</script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1280,"y":300,"wires":[[]]},{"id":"b09d0db34b263126","type":"inject","z":"862ec766f2af6f61","name":"counter","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"counter","payload":"(\t    $minimum := 1;\t    $maximum := 10;\t    $round(($random() * ($maximum-$minimum)) + $minimum, 0)\t)","payloadType":"jsonata","x":1090,"y":260,"wires":[["7d71bd185726b4cc"]]},{"id":"1976bccce7d9c2da","type":"inject","z":"862ec766f2af6f61","name":"status","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"status","payload":"(\t    $list := [\"normal\", \"fault\", \"running\", \"stopped\"]; \t    $rand := $floor($random() * $count($list));          \t    $list[$rand]\t)","payloadType":"jsonata","x":1095.892333984375,"y":308.8888854980469,"wires":[["7d71bd185726b4cc"]]},{"id":"a6f82f13d5f715f8","type":"inject","z":"862ec766f2af6f61","name":"error","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"error","payload":"(\t    $list := [\"overheat\", \"crash\", \"stall\", \"fuse\"]; \t    $rand := $floor($random() * $count($list));          \t    $list[$rand]\t)","payloadType":"jsonata","x":1090,"y":360,"wires":[["7d71bd185726b4cc"]]},{"id":"b1da20a2d7d13012","type":"ui-group","name":"Chart Tests","page":"94b0d43f03c8e17e","width":"12","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"94b0d43f03c8e17e","type":"ui-page","name":"Chart Tests","ui":"22ea43815413e748","path":"/chart-tests","icon":"home","layout":"flex","theme":"c2ff5ba1f92a0f0e","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":9,"className":"","visible":"true","disabled":"false"},{"id":"22ea43815413e748","type":"ui-base","name":"base","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"c2ff5ba1f92a0f0e","type":"ui-theme","name":"Default","colors":{"surface":"#ffffff","primary":"#6771f4","bgPage":"#e5dcdc","groupBg":"#ffffff","groupOutline":"#d39292"},"sizes":{"pagePadding":"6px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"6px","density":"default"}}]
1 Like

@Colin
It is not possible to output anything. The FINS node times out. No output because it never goes so far. The devices I send requests to lives under a LTE(4G) router. Some times the communication stops.

@Steve-Mcl
Together with your answer and a facepalm from me the UI doesn't reload when it doesn't need to any more.

I was using msg.data.someProp directly in the template.
I did do a check like you sugested Steve, in watch, but of course that doesn't help when I use it directly in the template without any checks. :face_with_peeking_eye:

For others. Don't use msg.payload.someProp directly in the template if you dont know what you are doing.
Declare them in the data section and update variable in the watch section.

Thanks a bunch Colin and Steve.

Best regards
Steffen

If all you want to do is to detect the fact that no data is received for a specified time then you can easily use a Trigger node for that. Configure it something like shown below and feed the incoming messages into it. If nothing is received for a minute it will send the timeout message.

Thanks Colin.

This is a simple solution I think. I will see if this might do the trick. I have another solution workin now. Might change it to your solution.

Best regards
Steffen

This approach is nice, if you have a widget that operates on different partial payloads from different event sources, but...

Is it possible that the data won't survive a refresh or page change?

I think Dashboard only stores the last message in the server-side state store. So upon reload, the widget can only restore the part based on the last partial message. In your example you end up having only one of state, counter or error set, depending on the last message it has received.

I faced the same problem and worked around it by always providing a complete message with all partial content. I used a join node that creates a merged object.
It's a bit overkill, but I couldn't get it working otherwise.

So I don't think partial persistent updates are possible at the moment. :thinking:

You could always nab the uib-cache node from uibuilder - that has more options. Though you would need to wire up a flow to trigger the cache replay, that uses a simple message. :blush: