Need help with ghost thermostat

  1. So the issue with stats getting out of sync seems to be due to msg.socketid if I delete that property the values update correctly, not sure if you can remove that within your template or if its added afterward by NR ?

  2. ambient_temperature: 20.7
    target_temperature: "19.5" change to a number

  3. rounding errors eg target temp of 20.1 and 20.2 is shown as 20.1, target temp of 20.3 and 20.4 is shown as 20.3

1 Like

I may be missing something , where are you deleting that property from?
And how are you deleting it?

@ghostmaster75 can you try this flow and see if you need to inject twice !

This was why I needed trigger nodes set to repeat an incoming message.

[{"id":"d6c548e700f619c9","type":"tab","label":"Thermostat","disabled":false,"info":"","env":[]},{"id":"ab8fb1c28921f1dd","type":"ui_template","z":"d6c548e700f619c9","group":"9c90a075584ede2b","name":"GhostThermostat","order":2,"width":8,"height":8,"format":"<style>\n    @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');\n    \n    svg {\n        transition: all .6s cubic-bezier(0.175, 0.885, 0.32, 1.2);\n    }\n\n    stop {\n        transition: all .5s;\n    }\n    \n\t{{'#GhostThermostat' + $id}} .led {\n    \t-webkit-transition: all 0.5s;\n    \ttransition: all 0.5s;\n    \tfill: url({{'#GhostThermostat' + $id + 'ledColor'}});\n    }\n    \n    {{'#GhostThermostat' + $id}} .fa-text {\n       font-family: FontAwesome !important; \n    }\n    {{'#GhostThermostat' + $id}} .dial {\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        user-select: none;\n    }\n    {{'#GhostThermostat' + $id}} .qGradient {\n       fill : url({{'#GhostThermostat' + $id + 'qGradient'}});\n    }\n    {{'#GhostThermostat' + $id}} .qGradientT {\n        fill : url({{'#GhostThermostat' + $id + 'qGradientT'}});\n    }\n    {{'#GhostThermostat' + $id}} .eGradient {\n        fill : url({{'#GhostThermostat' + $id + 'eGradient'}});\n    }\n    {{'#GhostThermostat' + $id}} .lbl {\n        font-family: 'Roboto', sans-serif;\n        text-anchor: middle;\n        fill : #ffffff;\n        clip-path: url({{'#GhostThermostat' + $id + 'qClip'}});\n    }\n    {{'#GhostThermostat' + $id}} .lblDial {\n        fill: #dddddd;\n    }\n    \n    {{'#GhostThermostat' + $id}} .lblAmbient {\n        font-weight: 400;\n        clip-path: url({{'#GhostThermostat' + $id + 'qClip'}});\n    }\n    \n    {{'#GhostThermostat' + $id}} .lblAmbient tspan {\n        font-weight: 400;\n    }\n    \n    {{'#GhostThermostat' + $id}} .lblTarget {\n        font-weight: 400;\n        fill: orange;\n    }\n    \n    {{'#GhostThermostat' + $id}} .lblTarget tspan {\n        font-weight: 400;\n        fill: orange;\n        clip-path: url({{'#GhostThermostat' + $id + 'qClip'}});\n    }    \n    \n    {{'#GhostThermostat' + $id}} .nodisplay {\n        display: none !important;\n    }\n    \n    {{'#GhostThermostat' + $id}} .icon {\n        font-family: FontAwesome !important;\n    }\n    \n    {{'#GhostThermostat' + $id}} .animate {\n        transition: all 0.5s;\n    }\n\n</style>\n<div id=\"{{'GhostThermostat' + $id}}\"></div> \n<script>\nvar mousedownID = -1;\nvar ghostThermostatDial = (function() {\n    console.log(\"START\");\n\n    function createSVGElement(tag, attributes, appendTo) {\n        var element = document.createElementNS('http://www.w3.org/2000/svg', tag);\n        attr(element, attributes);\n        if (appendTo) {\n            appendTo.appendChild(element);\n        }\n        return element;\n    }\n\n    function attr(element, attrs) {\n        for (var i in attrs) {\n            element.setAttribute(i, attrs[i]);\n        }\n    }\n\n    function setClass(el, className, state) {\n        el.classList[state ? 'add' : 'remove'](className);\n    }\n\n    return function(targetElement, options) {\n        console.log(\"RET FUN\");\n        var self = this;\n\n        /*\n         * Options\n         */\n        options = options || {};\n        options = {\n            diameter: options.diameter || 400,\n            mintemp: options.mintemp || 10, // Minimum value for target temperature\n            maxtemp: options.maxtemp || 30, // Maximum value for target temperature\n            ledColors: {\n                'off': 'rgb(143,141,141)',\n                'heating': 'rgb(255,128,0)',\n                'cooling': 'rgb(81,170,214)'\n            }, //Led Ring Colors\n            labels: {\n                ambient: \"AMBIENT\",\n                set: \"SET\",\n                mode: \"MODE\",\n                minus: \"-\",\n                plus: \"+\",\n                left: \"<\",\n                right: \">\"\n            },\n            onChangeState: options.onChangeState || function() {} // Function called when  switch state change\n        };\n\n        /*\n         * Properties\n         */\n        var properties = {\n            radius: options.diameter / 2,\n            modes: [{\n                    label: \"heating\",\n                    icon: \"\\uf06d\",\n                    color: \"orange\"\n                }, {\n                    label: 'cooling',\n                    icon: \"\\uf2dc\",\n                    color: \"rgb(81,170,214)\"\n                }, {\n                    label: \"off\",\n                    icon: \"\\uf011\",\n                    color: \"rgb(230,0,0)\"\n                }\n                /*, {\n\t\t\t\tlabel: 'away',\n\t\t\t\ticon: \"\\uf1ce\",\n\t\t\t\tcolor: \"gray\"\n\t\t\t} */\n            ],\n            modeNames: [\"heating\", \"cooling\", \"off\"],\n            swtitchStates: [\"heating\", \"cooling\", \"off\"]\n        };\n\n        /*\n         * Object state\n         */\n        var state = {\n            target_temperature: options.mintemp,\n            ambient_temperature: options.maxtemp,\n            mode: properties.modes.indexOf(properties.modes[0]),\n            switch_state: 'off',\n            away: false\n        };\n\n        /*\n         * Property getter / setters\n         */\n        Object.defineProperty(this, 'target_temperature', {\n            get: function() {\n                return state.target_temperature;\n            },\n            set: function(val) {\n                state.target_temperature = rangedTemperature(+val);\n                //render()\n            }\n        });\n\n        Object.defineProperty(this, 'ambient_temperature', {\n            get: function() {\n                return state.ambient_temperature;\n            },\n            set: function(val) {\n                state.ambient_temperature = +val;\n                render();\n            }\n        });\n\n        Object.defineProperty(this, 'mode_name', {\n            get: function() {\n                return properties.modeNames[state.mode];\n            },\n            set: function(val) {\n                if (properties.modeNames.indexOf(val) >= 0) {\n                    state.mode = properties.modeNames.indexOf(val);\n                    //render();\n                }\n            }\n        });\n\n        Object.defineProperty(this, 'switch_state', {\n            get: function() {\n                return state.switch_state;\n            },\n            set: function(val) {\n                if (properties.swtitchStates.indexOf(val) >= 0) {\n                    state.switch_state = val;\n                    //render();\n                }\n            }\n        });\n\n\n        function str2bool(strvalue) {\n            return (strvalue && typeof strvalue == 'string') ? (strvalue.toLowerCase() == 'true') : (strvalue == true);\n        }\n\n        Object.defineProperty(this, 'away', {\n            get: function() {\n                return state.away;\n            },\n            set: function(val) {\n                state.away = !!str2bool(val);\n                //render();\n            }\n        });\n\n\n        /*\n         * SVG\n         */\n        var svg = createSVGElement('svg', {\n            width: '100%', //options.diameter+'px',\n            height: '100%', //options.diameter+'px',\n            viewBox: '0 0 ' + options.diameter + ' ' + options.diameter,\n            class: 'dial'\n        }, targetElement);\n\n        // DEFS \n        var defs = createSVGElement('defs', null, svg);\n\n        var qgradient = createSVGElement('linearGradient', {\n            'id': targetElement.getAttribute('id') + 'qGradient',\n            gradientTransform: 'rotate(65)'\n        }, defs);\n        var stop = createSVGElement('stop', {\n            'offset': '50%',\n            'stop-color': 'rgb(86,89,94)'\n        }, qgradient);\n        var stop = createSVGElement('stop', {\n            'offset': '65%',\n            'stop-color': 'rgb(30,30,30)'\n        }, qgradient);\n\n        var qGradientT = createSVGElement('linearGradient', {\n            'id': targetElement.getAttribute('id') + 'qGradientT',\n            gradientTransform: 'rotate(65)'\n        }, defs);\n        var stop = createSVGElement('stop', {\n            'offset': '55%',\n            'stop-color': '#3b3e43',\n            'stop-opacity': '1'\n        }, qGradientT);\n        var stop = createSVGElement('stop', {\n            'offset': '90%',\n            'stop-color': 'rgb(0,0,0)',\n            'stop-opacity': '1'\n        }, qGradientT);\n\n        var clipPath = createSVGElement('clipPath', {\n            'id': targetElement.getAttribute('id') + 'qClip',\n        }, defs);\n        var circle = createSVGElement('circle', {\n            cx: properties.radius,\n            cy: properties.radius,\n            r: properties.radius - 25\n        }, clipPath);\n\n\n        var ledRingGradient = createSVGElement('radialGradient', {\n            'id': targetElement.getAttribute('id') + 'ledColor',\n            'cx': \"50%\",\n            'cy': \"50%\",\n            'r': \"95%\",\n            'fx': \"50%\",\n            'fy': \"50%\"\n        }, defs);\n        var ledRingGradientColorIn = createSVGElement('stop', {\n            'offset': '45%',\n            'stop-color': 'rgb(255,0,130)',\n            'stop-opacity': '1'\n        }, ledRingGradient);\n        var ledRingGradientColorOut = createSVGElement('stop', {\n            'offset': '65%',\n            'stop-color': 'rgb(0,0,0)',\n            'stop-opacity': '1'\n        }, ledRingGradient);\n\n        var egradient = createSVGElement('linearGradient', {\n            'id': targetElement.getAttribute('id') + 'eGradient',\n            gradientTransform: 'rotate(55)'\n        }, defs);\n        var stop = createSVGElement('stop', {\n            'offset': '55%',\n            'stop-color': '#888888',\n            'stop-opacity': '1'\n        }, egradient);\n        var stop = createSVGElement('stop', {\n            'offset': '95%',\n            'stop-color': '#333333',\n            'stop-opacity': '1'\n        }, egradient);\n\n        // DIAL\n        var circle = createSVGElement('circle', {\n            cx: properties.radius,\n            cy: properties.radius,\n            r: properties.radius,\n            class: 'eGradient'\n        }, svg);\n        var ledRing = createSVGElement('circle', {\n            cx: properties.radius,\n            cy: properties.radius,\n            r: properties.radius - 3,\n            'stroke': 'black',\n            'stroke-width': '1',\n            class: 'led'\n        }, svg);\n        var circle = createSVGElement('circle', {\n            cx: properties.radius,\n            cy: properties.radius,\n            r: properties.radius - 20,\n            class: 'qGradient'\n        }, svg);\n        var circle = createSVGElement('circle', {\n            cx: properties.radius,\n            cy: properties.radius,\n            r: properties.radius - 25,\n            class: 'qGradient'\n        }, svg);\n        var lblMain = createSVGElement('text', {\n            x: properties.radius,\n            y: 70,\n            class: 'lbl lblDial'\n        }, svg);\n        var lblMainText = document.createTextNode(options.labels.ambient);\n        lblMain.appendChild(lblMainText);\n\n        var lblAmbient = createSVGElement('text', {\n            x: properties.radius,\n            y: 210,\n            'font-size': '160',\n            class: 'lbl lblAmbient'\n        }, svg);\n        var lblAmbientText = document.createTextNode('21');\n        lblAmbient.appendChild(lblAmbientText);\n        var lblAmbientDec = createSVGElement('tspan', {\n            'font-size': '60',\n        }, lblAmbient);\n        var lblAmbientDecText = document.createTextNode('.5');\n        lblAmbientDec.appendChild(lblAmbientDecText);\n\n        var line = createSVGElement('line', {\n            x1: 55,\n            y1: properties.radius + 35,\n            x2: options.diameter - 55,\n            y2: properties.radius + 35,\n            'stroke': '#DDDDDD',\n            'stroke-width': '1',\n            'opacity': '0.8'\n        }, svg);\n\n        var lblLeft = createSVGElement('text', {\n            x: 125,\n            y: properties.radius + 75,\n            class: 'lbl lblDial'\n        }, svg);\n        var lblLeftText = document.createTextNode(options.labels.set);\n        lblLeft.appendChild(lblLeftText);\n\n        var lblTarget = createSVGElement('text', {\n            x: 125,\n            y: properties.radius + 115,\n            'font-size': '35',\n            class: 'lbl lblTarget',\n            'id': targetElement.getAttribute('id') + 'lblTarget'\n        }, svg);\n        var lblTargetText = document.createTextNode('20');\n        lblTarget.appendChild(lblTargetText);\n\n        var lblTargetDec = createSVGElement('tspan', {\n            'font-size': '20',\n        }, lblTarget);\n\n        var lblTargetDecText = document.createTextNode('.5');\n        lblTargetDec.appendChild(lblTargetDecText);\n\n        var lblRight = createSVGElement('text', {\n            x: options.diameter - 125,\n            y: properties.radius + 75,\n            class: 'lbl lblDial'\n        }, svg);\n        var lblRightText = document.createTextNode(options.labels.mode);\n        lblRight.appendChild(lblRightText);\n\n        var lblMode = createSVGElement('text', {\n            x: options.diameter - 125,\n            y: properties.radius + 115,\n            'font-size': '35',\n            class: 'lbl lblTarget icon',\n            'id' : targetElement.getAttribute('id') + 'lblMode'\n        }, svg);\n        var lblModeText = document.createTextNode(properties.modes[0].icon);\n        lblMode.appendChild(lblModeText);\n\n        var btnSet = createSVGElement('g', {\n            transform: 'translate(200,200)'\n        }, svg);\n        var btnLeft = createSVGElement('path', {\n            d: 'M0,40 L0,175   A175,175 0 0,1 -175,40    z',\n            fill: 'blue',\n            opacity: '0',\n            'id': targetElement.getAttribute('id') + 'btnLeft'\n        }, btnSet);\n        var btnRight = createSVGElement('path', {\n            d: 'M0,40 L175,40   A175,175 0 0,1    0,175  z',\n            fill: 'red',\n            opacity: '0',\n            'id': targetElement.getAttribute('id') + 'btnRight'\n        }, btnSet);\n\n\n\n        btnLeft.onclick = function() {\n            setTargetClick();\n        };\n\n        btnRight.onclick = function() {\n            setModeClick();\n        };\n\n        var targetPanel = false;\n        var modePanel = false;\n\n        var lblAmbientAttributes = {\n            x: lblAmbient.getAttribute('x'),\n            y: lblAmbient.getAttribute('y'),\n            size: lblAmbient.getAttribute('font-size')\n        };\n\n        var lblAmbientDecAttributes = {\n            x: lblAmbientDec.getAttribute('x'),\n            y: lblAmbientDec.getAttribute('y'),\n            size: lblAmbientDec.getAttribute('font-size')\n        };\n\n        var lblTargetAttributes = {\n            x: lblTarget.getAttribute('x'),\n            y: lblTarget.getAttribute('y'),\n            size: lblTarget.getAttribute('font-size')\n        };\n\n        var lblTargetDecAttributes = {\n            x: lblTargetDec.getAttribute('x'),\n            y: lblTargetDec.getAttribute('y'),\n            size: lblTargetDec.getAttribute('font-size')\n        };\n\n        var lblModeAttributes = {\n            x: lblMode.getAttribute('x'),\n            y: lblMode.getAttribute('y'),\n            size: lblMode.getAttribute('font-size')\n        };\n\n        var lblRightAttributes = {\n            x: lblRight.getAttribute('x'),\n            y: lblRight.getAttribute('y'),\n            size: lblRight.getAttribute('font-size')\n        };\n\n        var lblLeftAttributes = {\n            x: lblLeft.getAttribute('x'),\n            y: lblLeft.getAttribute('y'),\n            size: lblLeft.getAttribute('font-size')\n        };\n\n        render();\n\n        function setAmbientTemperature(ambientTemp) {\n            var splitValues = separateDecValue(ambientTemp);\n            lblAmbientText.textContent = splitValues.int;\n            lblAmbientDecText.textContent = splitValues.dec;\n        };\n\n\n        function calcTargetTemperature(operation) {\n            let currentTemp = Number(parseFloat(lblTargetText.textContent + lblTargetDecText.textContent)).toFixed(1);\n            let targetTemp = (operation == '-' ? Number(Number(currentTemp) - 0.5).toFixed(1) : Number(Number(currentTemp) + 0.5).toFixed(1));\n            targetTemp = rangedTemperature(targetTemp);\n            setTargetTemperature(targetTemp);\n            chkSwitchState();\n        };\n\n        function setTargetTemperature(targetTemp) {\n            var splitValues = separateDecValue(targetTemp);\n            lblTargetText.textContent = splitValues.int;\n            lblTargetDecText.textContent = splitValues.dec;\n            if (state.target_temperature != targetTemp) {\n                state.target_temperature = targetTemp\n                sendMsg();\n            };\n        };\n\n        function separateDecValue(floatFalue) {\n            var int = Math.floor(floatFalue);\n            var dec = Math.floor(((floatFalue % 1) * 10)) > 0 ? (\".\" + Math.floor(((floatFalue % 1) * 10))) : \"\";\n            return {\n                int,\n                dec\n            };\n        };\n\n        function rangedTemperature(temperature) {\n            temperature = temperature < options.mintemp ? options.maxtemp : temperature;\n            temperature = temperature > options.maxtemp ? options.mintemp : temperature;\n            return temperature;\n        };\n\n        function chkSwitchState() {\n            console.log(\"chkSwitchState\");\n            var switchState = state.switch_state;\n            switch (state.mode) {\n                case 0:\n                    switchState = state.ambient_temperature < state.target_temperature ? 'heating' : 'off';\n                    break;\n                case 1:\n                    switchState = state.ambient_temperature > state.target_temperature ? 'cooling' : 'off';\n                    break;\n                default:\n                    switchState = 'off';\n            };\n\n            ledRingGradientColorIn.setAttribute('stop-color', options.ledColors[state.switch_state]);\n\n            if (state.switch_state != switchState) {\n                state.switch_state = switchState;\n                sendMsg();\n            };\n        };\n\n\n        function resetButton() {\n            btnLeft.onmousedown = \"\";\n            btnLeft.onmouseup = \"\";\n            btnLeft.onclick = function() {\n                setTargetClick();\n            };\n            btnRight.onmousedown = \"\";\n            btnRight.onmouseup = \"\";\n            btnRight.onclick = function() {\n                setModeClick();\n            };\n        };\n\n        function switchMainView(element, originalAttributes, mainLabel, leftLabel, rightLabel, panelState) {\n            setClass(lblAmbient, \"nodisplay\", panelState);\n            setClass(lblMain, \"animate\", panelState);\n            setClass(lblLeft, \"animate\", panelState);\n            setClass(lblRight, \"animate\", panelState);\n            setClass(element, \"animate\", panelState);\n\n            lblMainText.textContent = panelState ? mainLabel : options.labels.ambient;\n            lblLeftText.textContent = panelState ? leftLabel : options.labels.set;\n\n            lblLeft.setAttribute('y', panelState ? Number(lblLeftAttributes.y) + 40 : lblLeftAttributes.y);\n            lblLeft.setAttribute('font-size', panelState ? \"3.5em\" : \"1em\");\n\n            lblRightText.textContent = panelState ? rightLabel : options.labels.mode;\n            lblRight.setAttribute('y', panelState ? Number(lblRightAttributes.y) + 40 : lblRightAttributes.y);\n            lblRight.setAttribute('font-size', panelState ? \"3.5em\" : \"1em\");\n\n            element.setAttribute('x', panelState ? lblAmbientAttributes.x : originalAttributes.x);\n            element.setAttribute('x', panelState ? lblAmbientAttributes.x : originalAttributes.x);\n            element.setAttribute('y', panelState ? lblAmbientAttributes.y : originalAttributes.y);\n            element.setAttribute('font-size', panelState ? lblAmbientAttributes.size : originalAttributes.size);\n\n        };\n\n\n        function setTargetClick() {\n\n            targetPanel = targetPanel ? false : true;\n            setClass(lblMode, \"nodisplay\", targetPanel);\n            switchMainView(lblTarget, lblTargetAttributes, options.labels.set, options.labels.minus, options.labels.plus, targetPanel);\n\n            lblTargetDec.setAttribute('font-size', targetPanel ? lblAmbientDecAttributes.size : lblTargetDecAttributes.size);\n\n            if (targetPanel) {\n                btnLeft.onclick = \"\";\n                btnRight.onclick = \"\";\n\n                btnLeft.onmousedown = function() {\n                    calcTargetTemperature(\"-\");\n                    if (mousedownID == -1) { //Prevent multimple loops!\n                        mousedownID = setInterval(calcTargetTemperature, 500, '-');\n                    }\n                };\n                btnLeft.onmouseup = function() {\n                    if (mousedownID != -1) { //Only stop if exists\n                        clearInterval(mousedownID);\n                        mousedownID = -1;\n                    }\n                };\n\n                btnRight.onmousedown = function() {\n                    calcTargetTemperature(\"+\");\n                    if (mousedownID == -1) { //Prevent multimple loops!\n                        mousedownID = setInterval(calcTargetTemperature, 500, '+');\n                    }\n                };\n                btnRight.onmouseup = function() {\n                    if (mousedownID != -1) { //Only stop if exists\n                        clearInterval(mousedownID);\n                        mousedownID = -1;\n                    }\n                };\n\n                lblTarget.onclick = function() {\n                    setTargetClick();\n                };\n            } else {\n                resetButton()\n            }\n        };\n\n        function setModeClick() {\n\n            modePanel = modePanel ? false : true;\n            setClass(lblTarget, \"nodisplay\", modePanel);\n            switchMainView(lblMode, lblModeAttributes, options.labels.mode, options.labels.left, options.labels.right, modePanel);\n\n            if (modePanel) {\n\n                btnLeft.onclick = function() {\n                    mode = state.mode;\n                    mode = --mode < 0 ? properties.modes.length - 1 : mode;\n                    console.log(\"MODE :\" + mode);\n                    setModeName(properties.modeNames[mode]);\n                    chkSwitchState();\n                    sendMsg();\n                };\n\n                btnRight.onclick = function() {\n                    mode = state.mode;\n                    mode = ++mode > properties.modes.length - 1 ? 0 : mode;\n                    console.log(\"MODE :\" + mode);\n                    setModeName(properties.modeNames[mode]);\n                    chkSwitchState();\n                    sendMsg();\n                };\n//                document.getElementById(targetElement.getAttribute('id') + \"lblMode\").onclick = function() {\n                lblMode.onclick = function() {\n                    setModeClick();\n                };\n            } else {\n                resetButton()\n            }\n        };\n\n        function setModeName(modeName) {\n            lblMode.textContent = properties.modes[properties.modeNames.indexOf(modeName)].icon;\n            lblMode.style.fill = properties.modes[properties.modeNames.indexOf(modeName)].color;\n            state.mode = properties.modeNames.indexOf(modeName);\n        };\n\n        function sendMsg() {\n            if (typeof options.onChangeState == 'function') {\n                options.onChangeState(state.switch_state);\n            }\n        };\n\n        function render() {\n            console.log(\"RENDER\");\n            setAmbientTemperature(self.ambient_temperature);\n            setTargetTemperature(self.target_temperature);\n            setModeName(self.mode_name);\n            chkSwitchState();\n        };\n\n    };\n})();\n\nvar initializing = true;\n(function(scope) {\n    console.log(\"scope.id = GhostThermostat\" + scope.$id);\n    $(function() {\n        var ghostThermostat = new ghostThermostatDial(document.getElementById('GhostThermostat' + scope.$id), {\n            onChangeState: function() {\n                var p = {\n                    \"ambient_temperature\": ghostThermostat.ambient_temperature,\n                    \"target_temperature\": ghostThermostat.target_temperature,\n                    \"mode\": ghostThermostat.mode_name,\n                    \"switch_state\": ghostThermostat.switch_state,\n                    \"away\": ghostThermostat.away\n                };\n                scope.send({\n                    topic: \"changed_state\",\n                    payload: p\n                });\n            }\n        });\n\n\n        scope.$watch('msg', function(data) {\n            if (initializing) {\n                initializing = false;\n            } else {\n                ghostThermostat.ambient_temperature = data.payload.ambient_temperature || ghostThermostat.ambient_temperature;\n                ghostThermostat.target_temperature = data.payload.target_temperature || ghostThermostat.target_temperature;\n                ghostThermostat.mode_name = data.payload.mode || ghostThermostat.mode_name;\n                ghostThermostat.switch_state = data.payload.switch_state || ghostThermostat.switch_state;\n                ghostThermostat.away = data.payload.away || ghostThermostat.away;\n            }\n        });\n    });\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","className":"","x":550,"y":180,"wires":[["9c3590c3c43db0d2"]],"icon":"font-awesome/fa-tachometer"},{"id":"801173ed726f38e6","type":"function","z":"d6c548e700f619c9","name":"Data","func":"var data = {\n    'ambient_temperature': 25,\n    'target_temperature': msg.set ,\n    'mode': msg.mode,\n    \n}\nmsg.payload = data;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":180,"wires":[["ab8fb1c28921f1dd","3dee33b64b78ca76"]]},{"id":"1d3f37a34b7850b2","type":"inject","z":"d6c548e700f619c9","name":"cooling 10","props":[{"p":"set","v":"10","vt":"num"},{"p":"mode","v":"cooling","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":160,"y":160,"wires":[["801173ed726f38e6"]]},{"id":"9c3590c3c43db0d2","type":"debug","z":"d6c548e700f619c9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":710,"y":180,"wires":[]},{"id":"3dee33b64b78ca76","type":"debug","z":"d6c548e700f619c9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":510,"y":140,"wires":[]},{"id":"9929e7661969d500","type":"inject","z":"d6c548e700f619c9","name":"heating 20","props":[{"p":"set","v":"20","vt":"num"},{"p":"mode","v":"heating","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":160,"y":200,"wires":[["801173ed726f38e6"]]},{"id":"c07a15415d204630","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":1,"width":1,"height":1},{"id":"2dbe813b27d7043f","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":3,"width":1,"height":1},{"id":"96535aa639b7bffd","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":4,"width":1,"height":1},{"id":"a1121f516f0aa3db","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":5,"width":1,"height":1},{"id":"6c85c463fe00fd15","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":6,"width":1,"height":1},{"id":"eebd2c9acf40e78f","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":7,"width":1,"height":1},{"id":"3975f04253ba1b84","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":8,"width":1,"height":1},{"id":"d41cc8a579c21c61","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":9,"width":1,"height":1},{"id":"667962265895139c","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":10,"width":1,"height":1},{"id":"3f25f8dcaaa3a3df","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":11,"width":1,"height":1},{"id":"a0fdb9cdd6c6ea96","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":12,"width":1,"height":1},{"id":"425c9f1cf3b24637","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":13,"width":1,"height":1},{"id":"28609107bf40e9e3","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":14,"width":1,"height":1},{"id":"069be399c44c6767","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":15,"width":1,"height":1},{"id":"a06f0dc34905dd80","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":16,"width":1,"height":1},{"id":"4ddfaa0f576f1286","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"9c90a075584ede2b","order":17,"width":1,"height":1},{"id":"0737f7d6ed75619a","type":"ui_spacer","z":"d6c548e700f619c9","name":"spacer","group":"64a50a7178579a49","order":1,"width":12,"height":1},{"id":"9c90a075584ede2b","type":"ui_group","name":"Thermostat","tab":"f394bc89e321d6f1","order":1,"disp":false,"width":"10","collapse":false,"className":""},{"id":"64a50a7178579a49","type":"ui_group","name":"Menu","tab":"f394bc89e321d6f1","order":2,"disp":false,"width":"14","collapse":false,"className":""},{"id":"f394bc89e321d6f1","type":"ui_tab","name":"Thermostat","icon":"dashboard","disabled":false,"hidden":false}]

If you click on any dashboard widget the sent message includes the socket id.
If you look in your browser address bar you can see it there.

eg

http://192.168.x.xxx:1880/ui/#!/24?socketid=iyu-n3FDErrCTsRqAAi9

example message -

02/05/2022, 17:21:40node: 9c3590c3c43db0d2
changed_state : msg : Object
object
topic: "changed_state"
payload: object
socketid: "iyu-n3FDErrCTsRqAAi9"
_msgid: "557b21a6a84b203b"

if you then pass a message back to your dashboard widget it will ONLY be sent to the one matching that socket id. This means that if you have muliple browsers open the others will not get the message.

You can remove that property from the message eg in a function node - delete msg.socketid

Then the message will go to ALL open browsers.

1 Like

Hello :slight_smile:

I got a problem where the thermostat only works if I am on that tab in the UI, it does nothing if ambient temperature changes over or under target temperature until I click on the tab with the thermostat on...

Any idea what could be wrong?

-edit

Never mind, I of course need a thermostat like node-red-contrib-dynamic-thermostat (node) - Node-RED (nodered.org) to handle the automation of the heater when the dashboard UI isn't loaded.

-edit

1 Like

sorry late response i was going to say you need to use different for heating.
I use a different way for heating using pwm.
what temp sensors are you running and what are you running from?

[{"id":"dd13811052cd95db","type":"ui_text","z":"1f851c1.28701e4","group":"3e7d17b4939773da","order":2,"width":6,"height":1,"name":"","label":"Boil Kettle Temp","format":"{{msg.payload}}","layout":"row-spread","x":1304.0000495910645,"y":722.0000419616699,"wires":[]},{"id":"b4772ad2ffb4e26d","type":"switch","z":"1f851c1.28701e4","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"Boillimit","vt":"global"},{"t":"eq","v":"Boillimit","vt":"global"},{"t":"gt","v":"Boillimit","vt":"global"}],"checkall":"true","repair":false,"outputs":3,"x":1279,"y":675.9999976158142,"wires":[[],[],["91c9f499b31f0d8b"]]},{"id":"91c9f499b31f0d8b","type":"change","z":"1f851c1.28701e4","name":"Higher than limit value (remove power from element)","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1609,"y":675.9999976158142,"wires":[["b0185f3415c2f493"]]},{"id":"13ee81c3765d0c55","type":"change","z":"1f851c1.28701e4","name":"","rules":[{"t":"set","p":"Boillimit","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1239,"y":555.9999976158142,"wires":[[]]},{"id":"b0185f3415c2f493","type":"change","z":"1f851c1.28701e4","name":"power to 0","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1971.000072479248,"y":660.9999656677246,"wires":[["be5724dedc446fde","68d241873fe78381","d9017e1b5e9639d6","dba09c05f4f0d46f","19659e94c32401e1"]]},{"id":"2ef1702741b2be56","type":"ui_switch","z":"1f851c1.28701e4","name":"Power on/off","label":"","tooltip":"","group":"3e7d17b4939773da","order":5,"width":6,"height":1,"passthru":false,"decouple":"false","topic":"","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":2399,"y":675.9999976158142,"wires":[["834807dc706d6596","1d0472f27caff6aa"]]},{"id":"4353f27acae1c4ee","type":"change","z":"1f851c1.28701e4","name":"blocked sends message false","rules":[{"t":"set","p":"blocked","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2839,"y":695.9999976158142,"wires":[["3e84ddf009e80aee"]]},{"id":"be5724dedc446fde","type":"change","z":"1f851c1.28701e4","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":2189,"y":675.9999976158142,"wires":[["2ef1702741b2be56"]]},{"id":"834807dc706d6596","type":"switch","z":"1f851c1.28701e4","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2579,"y":675.9999976158142,"wires":[["4eebf4e34767f9fa","7ab9a1b8b5a1099a"],["4353f27acae1c4ee","7ab9a1b8b5a1099a"]]},{"id":"4eebf4e34767f9fa","type":"change","z":"1f851c1.28701e4","name":"unblocked gives message true","rules":[{"t":"set","p":"blocked","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2839,"y":655.9999976158142,"wires":[[]]},{"id":"1d0472f27caff6aa","type":"ui_led","z":"1f851c1.28701e4","order":6,"group":"3e7d17b4939773da","width":6,"height":1,"label":"","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"#ff0000","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"Boil ","x":2579,"y":635.9999976158142,"wires":[]},{"id":"2c62240ad1d90e5c","type":"ui_text","z":"1f851c1.28701e4","group":"3e7d17b4939773da","order":7,"width":6,"height":1,"name":"Boil Temperature shut off  value ","label":"Boil Temperature shut off  value ","format":"{{msg.payload}}","layout":"row-left","x":899,"y":515.9999976158142,"wires":[]},{"id":"56bbd58840cb8e5c","type":"ui_text","z":"1f851c1.28701e4","group":"3e7d17b4939773da","order":8,"width":6,"height":1,"name":"Boil Power","label":"Boil Power","format":"{{msg.payload}}","layout":"row-spread","x":2409,"y":635.9999976158142,"wires":[]},{"id":"68d241873fe78381","type":"ui_gauge","z":"1f851c1.28701e4","name":"Boil Current power gauge","group":"3e7d17b4939773da","order":4,"width":6,"height":6,"gtype":"gage","title":"Boil Current power","label":"%","format":"{{value}}","min":0,"max":"100","colors":["#ff0000","#00ff00","#ff0000"],"seg1":"40","seg2":"60","x":2339,"y":515.9999976158142,"wires":[]},{"id":"d9017e1b5e9639d6","type":"rpi-gpio out","z":"1f851c1.28701e4","d":true,"name":"Boil PM","pin":"22","set":"","level":"0","freq":"1","out":"pwm","bcm":true,"x":2417.000099182129,"y":556.9999942779541,"wires":[]},{"id":"92614257f8253241","type":"switch","z":"1f851c1.28701e4","name":"","property":"blocked","propertyType":"global","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2068.9999504089355,"y":399.00004482269287,"wires":[["68d241873fe78381","d9017e1b5e9639d6","19659e94c32401e1"],[]]},{"id":"7ab9a1b8b5a1099a","type":"change","z":"1f851c1.28701e4","name":"Reset at On and Off (safety feature)","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2849,"y":607,"wires":[["67421d9390862223"]]},{"id":"dba09c05f4f0d46f","type":"ui_text_input","z":"1f851c1.28701e4","name":"Boil Power Setting","label":"Boil Power Setting","tooltip":"","group":"3e7d17b4939773da","order":10,"width":6,"height":1,"passthru":true,"mode":"text","delay":300,"topic":"topic","x":1899,"y":555.9999976158142,"wires":[["92614257f8253241"]]},{"id":"097f777c6a3a78ad","type":"ui_button","z":"1f851c1.28701e4","name":"Boil Power to 0 (Soft Stop)","group":"3e7d17b4939773da","order":12,"width":12,"height":1,"passthru":true,"label":"Boil Power to 0 (Soft Stop)","tooltip":"","color":"","bgcolor":"","icon":"","payload":"0","payloadType":"str","topic":"topic","x":1889,"y":515.9999976158142,"wires":[["92614257f8253241","dba09c05f4f0d46f"]]},{"id":"96f57f8284c20560","type":"ui_text_input","z":"1f851c1.28701e4","name":"Boil Temperature shut off  value ","label":"","tooltip":"Boil","group":"3e7d17b4939773da","order":9,"width":6,"height":1,"passthru":true,"mode":"text","delay":300,"topic":"topic","x":899,"y":555.9999976158142,"wires":[["13ee81c3765d0c55"]]},{"id":"c5d6be69774adae3","type":"inject","z":"1f851c1.28701e4","name":"Shut off test (simulated temperature input celcius)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"103","payloadType":"num","x":969,"y":675.9999976158142,"wires":[["b4772ad2ffb4e26d"]]},{"id":"67421d9390862223","type":"link out","z":"1f851c1.28701e4","name":"Send 0 At Power Off","mode":"link","links":["db3aa04899b62ab9"],"x":3024,"y":615.9999976158142,"wires":[]},{"id":"db3aa04899b62ab9","type":"link in","z":"1f851c1.28701e4","name":"Receive 0 At Power Off","links":["67421d9390862223"],"x":1714,"y":555.9999976158142,"wires":[["dba09c05f4f0d46f"]]},{"id":"90786ef2e8c4724a","type":"inject","z":"1f851c1.28701e4","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":2199,"y":635.9999976158142,"wires":[["2ef1702741b2be56"]]},{"id":"3e84ddf009e80aee","type":"link out","z":"1f851c1.28701e4","name":"Send geblokkeerd geeft false bericht","mode":"link","links":["cd2aad25dfbeb375"],"x":3024,"y":695.9999976158142,"wires":[]},{"id":"cd2aad25dfbeb375","type":"link in","z":"1f851c1.28701e4","name":"Receive geblokkeerd geeft false bericht","links":["3e84ddf009e80aee"],"x":1814,"y":635.9999976158142,"wires":[["b0185f3415c2f493"]]},{"id":"4e9b4d84eca3fd6a","type":"debug","z":"1f851c1.28701e4","name":"boil","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1272.0000801086426,"y":756.0000438690186,"wires":[]},{"id":"56c51ef94bcf5ad2","type":"ui_template","z":"1f851c1.28701e4","group":"3e7d17b4939773da","name":"Boil Kettle","order":3,"width":6,"height":6,"format":"<div id=\"{{'gauge_'+$id}}\" class=\"g-container\" style=\"--gauge-value:0; --container-size:6; --gn-distance:14; --ga-tick-count:22; --ga-subtick-count:220; --g-unit:''Ā°F'\">\n    <div id=\"bgr\" class=\"g-body\">\n        <div class=\"g-ring\">\n            <div class=\"g-rivets\">\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n            </div>\n            <div class=\"g-plate\">\n                <div class=\"g-ticks\">\n                    <div class=\"g-tick\" style=\"--ga-tick:1;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:3;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:5;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:7;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:9;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:11;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:13;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:15;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:17;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:19;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:21;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:23;\"></div>\n                </div>\n                <div class=\"g-ticks\">\n                    <div class=\"g-subtick\" style=\"--ga-tick:11;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:31;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:51;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:71;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:91;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:111;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:131;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:151;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:171;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:191;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:211;\"></div>\n                    \n                </div>\n               <div class=\"g-nums\">\n                    <div class=\"g-num\" style=\"--ga-tick:1;\" >0</div>\n                    <div class=\"g-num\" style=\"--ga-tick:3;\">20</div>\n                    <div class=\"g-num\" style=\"--ga-tick:5;\">40</div>\n                    <div class=\"g-num\" style=\"--ga-tick:7;\">60</div>\n                    <div class=\"g-num\" style=\"--ga-tick:9;\">80</div>\n                    <div class=\"g-num\" style=\"--ga-tick:11;\">100</div>\n                    <div class=\"g-num\" style=\"--ga-tick:13;\">120</div>\n                    <div class=\"g-num\" style=\"--ga-tick:15;\">140</div>\n                    <div class=\"g-num\" style=\"--ga-tick:17;\">160</div>\n                    <div class=\"g-num\" style=\"--ga-tick:19;\">180</div>\n                    <div class=\"g-num\" style=\"--ga-tick:21;\">200</div>\n                    <div class=\"g-num\" style=\"--ga-tick:23;\">220</div>\n                    \n                </div>\n                <div class=\"g-label\">Boil Kettle </div>\n                <div class=\"g-needle\"></div>\n                <div class=\"g-needle-ring\"></div>\n                <div id=\"{{'gauge_val_'+$id}}\" class=\"g-val\"></div>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n(function(scope) {\n    let min = 0;\n    let max = 100;\n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      // Do something when msg arrives\n        const v = Math.floor(((msg.payload - min) / (max - min)) * (100 * 0.46));\n        const d = Math.floor(((msg.payload - min) / (max - min)) * 100);\n        document.getElementById('gauge_'+scope.$id).style.setProperty('--gauge-value', v);\n        document.getElementById('gauge_val_'+scope.$id).innerText = d;\n    }\n   \n  });\n})(scope);\n</script>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":1282.4480209350586,"y":791.701379776001,"wires":[[]]},{"id":"1981e1b781103b1a","type":"ui_template","z":"1f851c1.28701e4","group":"","name":"gauge-style","order":1,"width":0,"height":0,"format":"<style id=\"hotNipi-gauge\">\n:root{\n    --needle-color:#001100;\n    --needle-second-color:#ff0000;\n    --red-ticks:9;\n    --sec-high-color:#ff00004f;\n    --sec-normal-color:#00ff004f;\n    --sec-low-color:#0000ff4f;\n    --sec-sub-color:#0000ff8f;\n}\n.g-container {\n    padding-top: 3px;\n    padding-bottom: 3px;\n    width: 100%;\n    height: 100%;\n    position: relative;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    user-select:none;\n}\n    \n.g-body {\n    width: 98%;\n    height: 98%;\n    border-radius: 15%;\n    position: relative;\n    display: flex;\n    align-content: center;\n    align-items: center;\n    justify-content: center;\n    box-shadow: 0px 5px 8px #00000045;\n    background: linear-gradient(0deg, rgba(78,78,78,1) 0%, rgba(215,215,215,1) 99%, rgba(236,236,236,1) 100%);\n}\n\n.g-body::before {    \n    content: \"\";\n    background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc1JyBoZWlnaHQ9JzUnPgogIDxyZWN0IHdpZHRoPSc1JyBoZWlnaHQ9JzUnIGZpbGw9J3doaXRlJy8+CiAgPHBhdGggZD0nTTAgNUw1IDBaTTYgNEw0IDZaTS0xIDFMMSAtMVonIHN0cm9rZT0nIzg4OCcgc3Ryb2tlLXdpZHRoPScxJy8+Cjwvc3ZnPg==\"); \n    background-repeat: repeat;\n    position: absolute;\n    top: 0px;\n    right: 0px;\n    bottom: 0px;\n    left: 0px;\n    opacity: 0.1;\n    border-radius:15%;\n}\n\n.g-sector{\n    width: 100%;\n    height: 100%;\n    position: absolute;\n    top: 50%;\n    left: 50%;\n   \n    transform-origin: 0 0;\n}\n.g-sector-high {\n    background: var(--sec-high-color);\n    transform: rotate(328deg) skew(12deg, 23deg);\n}\n.g-sector-normal {\n    background:var(--sec-normal-color);\n    transform: rotate(328deg) skew(12deg, 23deg);\n}\n.g-sector-low {\n    background:var(--sec-low-color);\n    transform: rotate(328deg) skew(12deg, 23deg);\n}\n.g-sector-sub {\n    background:var(--sec-sub-color);\n    transform: rotate(328deg) skew(12deg, 23deg);\n}\n\n.g-ring {\n    width: 94%;\n    height: 94%;\n    border-radius: 50%;\n    position: relative;\n    display: flex;\n    align-content: center;\n    align-items: center;\n    justify-content: center;\n    background: linear-gradient(180deg, rgba(78,78,78,1) 0%, rgba(215,215,215,1) 99%, rgba(236,236,236,1) 100%);\n}\n.g-plate {\n    overflow: hidden;\n    width: 93%;\n    height: 93%;\n    border-radius: 50%;\n    position: relative;\n    box-shadow: inset 0 0 15px #000000a3;\n    background: radial-gradient(circle, #dd8400 3%, rgb(196 205 209) 1%, rgb(177 183 186) 40%, rgb(191 193 194) 100%);\n}\n.g-plate-bright {\n    overflow:hidden;\n    width: 90%;\n    height: 90%;\n    border-radius: 50%;\n    position: relative;\n    box-shadow: inset 0 0 10px yellow;\n    background: radial-gradient(circle, #ed9400 3%, rgb(255, 255, 255) 1%, rgb(255, 255, 158) 40%, rgb(148, 154, 160) 100%);\n}\n\n\n.g-ticks {\n    position: absolute;\n    top:0;\n    left:0;\n    width: 100%;\n    height: 100%;\n    filter: drop-shadow(2px 4px 6px black);\n}\n.g-tick {\n    transform: rotate(calc(calc(270deg / var(--ga-tick-count)) * var(--ga-tick) - calc(calc(270deg / var(--ga-tick-count)) + 45deg)));\n    background: #000;\n    position: relative;\n    left: 0;\n    top: 50%;\n    width: 100%;\n    height: 1px;\n    margin-bottom: -1px;\n    background: linear-gradient(90deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 2%,rgb(0 0 0 / 60%) 2%,rgb(0 0 0 / 60%) 10%,rgba(0,0,0,0) 10%);\n}\n.g-subtick {\n    transform: rotate(calc(calc(270deg / var(--ga-subtick-count)) * var(--ga-tick) - calc(calc(270deg / var(--ga-subtick-count)) + 45deg)));\n    background: #000;\n    position: relative;\n    left: 0;\n    top: 50%;\n    width: 100%;\n    height: 1px;\n    margin-bottom: -1px;\n    background: linear-gradient(90deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 2%,rgb(0 0 0 / 40%) 2%,rgb(0 0 0 / 40%) 6%,rgba(0,0,0,0) 6%);\n}\n\n.g-num {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    text-align: center;\n    transform: translate(-50%, -50%) rotate(calc(calc(270deg / var(--ga-tick-count)) * var(--ga-tick) - calc(calc(270deg /\n    var(--ga-tick-count)) + 45deg))) translate(calc(-1px * var(--container-size) * var(--gn-distance))) rotate(calc(calc(270deg / var(--ga-tick-count)) * var(--ga-tick) *-1 -\n    calc(calc(270deg / var(--ga-tick-count))*-1 - 45deg)));\n}\n\n.g-nums {\n    position: absolute;\n    top: 0;\n    width: 100%;\n    height: 100%;\n    color: #000000a1;\n    font-size: calc(var(--container-size) * 20%);\n    font-weight: 500;\n    filter: drop-shadow(2px 4px 10px black);\n}\n.g-needle {\n    transform: rotate(calc(270deg * calc(var(--gauge-value, 0deg) / 100) - 45deg));\n    transition: transform 1s;\n    background: #000;\n    position: absolute;\n    left: 0;\n    top: 49%;\n    width: 100%;\n    height: 2%;\n    filter:drop-shadow(0px 1px 3px #00000080);\n    background: linear-gradient(90deg,rgba(2,0,36,0) 0,rgba(0,0,0,0) 15%,var(--needle-color) 15%,var(--needle-color) 50%,rgba(0,0,0,0) 50%);\n}\n.g-needle-second {\n    transform: rotate(calc(270deg * calc(var(--gauge-value-second, 0deg) / 100) - 45deg));\n    transition: transform 1s;\n    background: #000;\n    position: absolute;\n    left: 0;\n    top: 49%;\n    width: 100%;\n    height: 2%;\n    filter:drop-shadow(0px 1px 3px #ff000080);  \n    background: linear-gradient(90deg,rgba(2,0,36,0) 0,rgba(0,0,0,0) 15%,var(--needle-second-color) 15%,var(--needle-second-color) 50%,rgba(0,0,0,0) 50%);\n}\n.g-needle-ring {\n    position: absolute;\n    width: calc(var(--container-size) * 1.5%);\n    height: calc(var(--container-size) * 1.5%);\n    top: 50%;\n    left: 50%;\n    background: var(--needle-color);\n    border-radius: 50%;\n    transform: translate(-50%, -50%);\n    box-shadow: 0 1px 4px #0000009c;\n}\n.g-val{\n    position: absolute;\n    text-align: center;\n    left: 50%;\n    bottom: 8%;\n    width: 80px;\n    transform: translateX(-50%);\n    font-family: monospace;\n    font-size:  calc(var(--container-size) * 40%);\n    color: #000000a1;\n    filter: drop-shadow(2px 3px 2px #00000050);\n}\n\n.g-label{\n    position: absolute;\n    text-align: center;\n    left: 50%;\n    top: 58%;\n    width: 100%;\n    transform: translateX(-50%);\n    font-family: monospace;\n    font-size:  calc(var(--container-size) * 18%);\n    color: #000000a1;\n    filter: drop-shadow(2px 3px 2px #00000080);\n}\n.g-label::after{\n    content: var(--g-unit);\n    font-size: clamp(1em,calc(var(--container-size) * 40%),1.5em);\n    position: absolute;\n    left: 0;\n    top: 100%;\n    width: 100%;\n    height: 100%;\n}\n.g-rivets{\n    position: absolute;\n    left:0;\n    top:0;\n    width: 100%;\n    height: 100%;\n}\n\n.g-rivet {\n    position: absolute;\n    width: calc(var(--container-size) * 2px);\n    height: calc(var(--container-size) * 2px);\n    border-radius: 50px;\n    background: linear-gradient(135deg, #adadad 0%,#d3d3d3 51%,#d1d1d1 68%,#8c8c8c 100%);\n    box-shadow: 0px 2px 4px #000, -1px -1px 5px rgba(0,0,0,0.2);\n    border: 1px solid rgba(255,255,255,0.1);\n}\n.g-rivet:nth-child(1){\n    top:calc(var(--container-size) * 1.5px);\n    left:calc(var(--container-size) * 1.5px);\n}\n.g-rivet:nth-child(2){\n    top:calc(var(--container-size) * 1.5px);\n    right:calc(var(--container-size) * 1.5px);\n}\n.g-rivet:nth-child(3){\n    bottom:calc(var(--container-size) * 1.5px);\n    left:calc(var(--container-size) * 1.5px);\n}\n.g-rivet:nth-child(4){\n    bottom:calc(var(--container-size) * 1.5px);\n    right:calc(var(--container-size) * 1.5px);\n}\n\n.g-rivet::before {\n    content: '';\n    position: absolute;\n    left: 50%;\n    top: 50%;\n    width: calc(var(--container-size) * 10%);\n    height: calc(var(--container-size) * 10%);\n    box-shadow: inset 0px 1px 2px #222;\n    transform: translate(-50%, -50%);\n    border-radius: 50%;\n    border-bottom: 1px solid rgba(255,255,255,0.3);\n}\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":1867.1143798828125,"y":774.0347232818604,"wires":[[]]},{"id":"09ad4768ace80c25","type":"ui_template","z":"1f851c1.28701e4","group":"34c8e5f1.3881fa","name":"0-220","order":50,"width":4,"height":1,"format":"<div id=\"{{'gauge_'+$id}}\" class=\"g-container\" style=\"--gauge-value:0; --container-size:6; --gn-distance:14; --ga-tick-count:11; --ga-subtick-count:22; --g-unit:''Ā°F'\">\n    <div id=\"bgr\" class=\"g-body\">\n        <div class=\"g-ring\">\n            <div class=\"g-rivets\">\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n            </div>\n            <div class=\"g-plate\">\n                <div class=\"g-ticks\">\n                    <div class=\"g-tick\" style=\"--ga-tick:1;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:2;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:3;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:4;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:5;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:6;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:7;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:8;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:9;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:10;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:11;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:12;\"></div>\n                </div>\n                <div class=\"g-ticks\">\n                    <div class=\"g-subtick\" style=\"--ga-tick:2;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:4;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:6;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:8;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:10;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:12;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:14;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:16;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:18;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:20;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:22;\"></div>\n                    \n                </div>\n               <div class=\"g-nums\">\n                    <div class=\"g-num\" style=\"--ga-tick:1;\" >0</div>\n                    <div class=\"g-num\" style=\"--ga-tick:2;\">20</div>\n                    <div class=\"g-num\" style=\"--ga-tick:3;\">40</div>\n                    <div class=\"g-num\" style=\"--ga-tick:4;\">60</div>\n                    <div class=\"g-num\" style=\"--ga-tick:5;\">80</div>\n                    <div class=\"g-num\" style=\"--ga-tick:6;\">100</div>\n                    <div class=\"g-num\" style=\"--ga-tick:7;\">120</div>\n                    <div class=\"g-num\" style=\"--ga-tick:8;\">140</div>\n                    <div class=\"g-num\" style=\"--ga-tick:9;\">160</div>\n                    <div class=\"g-num\" style=\"--ga-tick:10;\">180</div>\n                    <div class=\"g-num\" style=\"--ga-tick:11;\">200</div>\n                    <div class=\"g-num\" style=\"--ga-tick:12;\">220</div>\n                    \n                </div>\n                <div class=\"g-label\">Boil Kettle </div>\n                <div class=\"g-needle\"></div>\n                <div class=\"g-needle-ring\"></div>\n                <div id=\"{{'gauge_val_'+$id}}\" class=\"g-val\"></div>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n(function(scope) {\n    let min = 0;\n    let max = 220;\n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      // Do something when msg arrives\n        const val = msg.payload\n        const v = ((val - min) / (max - min)) * 100;\n        document.getElementById('gauge_'+scope.$id).style.setProperty('--gauge-value', v);\n        document.getElementById('gauge_val_'+scope.$id).innerText = val.toFixed(1);\n    }\n   \n  });\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":1877.1144104003906,"y":816.0346984863281,"wires":[[]]},{"id":"4162dc019210439f","type":"link in","z":"1f851c1.28701e4","name":"","links":["4ccb4bd562321ec6"],"x":707.5626888275146,"y":757.8950939178467,"wires":[["9cb40896823b2041"]]},{"id":"9cb40896823b2041","type":"change","z":"1f851c1.28701e4","name":"set payload to Temperature value","rules":[{"t":"move","p":"payload.Temperature","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1008.5642852783203,"y":760.4461708068848,"wires":[["56c51ef94bcf5ad2","dd13811052cd95db","4e9b4d84eca3fd6a","b4772ad2ffb4e26d","99750e52f3bde5f1"]]},{"id":"19659e94c32401e1","type":"mqtt out","z":"1f851c1.28701e4","name":"","topic":"Boilswitch","qos":"2","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"8db3fac0.99dd48","x":2402.2327880859375,"y":419.1901108764648,"wires":[]},{"id":"99750e52f3bde5f1","type":"ui_template","z":"1f851c1.28701e4","group":"2b4c4afbaeb3db97","name":"Boil Kettle","order":2,"width":6,"height":6,"format":"<div id=\"{{'gauge_'+$id}}\" class=\"g-container\" style=\"--gauge-value:0; --container-size:6; --gn-distance:14; --ga-tick-count:22; --ga-subtick-count:220; --g-unit:''Ā°F'\">\n    <div id=\"bgr\" class=\"g-body\">\n        <div class=\"g-ring\">\n            <div class=\"g-rivets\">\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n                <div class=g-rivet></div>\n            </div>\n            <div class=\"g-plate\">\n                <div class=\"g-ticks\">\n                    <div class=\"g-tick\" style=\"--ga-tick:1;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:3;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:5;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:7;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:9;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:11;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:13;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:15;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:17;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:19;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:21;\"></div>\n                    <div class=\"g-tick\" style=\"--ga-tick:23;\"></div>\n                </div>\n                <div class=\"g-ticks\">\n                    <div class=\"g-subtick\" style=\"--ga-tick:11;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:31;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:51;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:71;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:91;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:111;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:131;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:151;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:171;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:191;\"></div>\n                    <div class=\"g-subtick\" style=\"--ga-tick:211;\"></div>\n                    \n                </div>\n               <div class=\"g-nums\">\n                    <div class=\"g-num\" style=\"--ga-tick:1;\" >0</div>\n                    <div class=\"g-num\" style=\"--ga-tick:3;\">20</div>\n                    <div class=\"g-num\" style=\"--ga-tick:5;\">40</div>\n                    <div class=\"g-num\" style=\"--ga-tick:7;\">60</div>\n                    <div class=\"g-num\" style=\"--ga-tick:9;\">80</div>\n                    <div class=\"g-num\" style=\"--ga-tick:11;\">100</div>\n                    <div class=\"g-num\" style=\"--ga-tick:13;\">120</div>\n                    <div class=\"g-num\" style=\"--ga-tick:15;\">140</div>\n                    <div class=\"g-num\" style=\"--ga-tick:17;\">160</div>\n                    <div class=\"g-num\" style=\"--ga-tick:19;\">180</div>\n                    <div class=\"g-num\" style=\"--ga-tick:21;\">200</div>\n                    <div class=\"g-num\" style=\"--ga-tick:23;\">220</div>\n                    \n                </div>\n                <div class=\"g-label\">Boil Kettle </div>\n                <div class=\"g-needle\"></div>\n                <div class=\"g-needle-ring\"></div>\n                <div id=\"{{'gauge_val_'+$id}}\" class=\"g-val\"></div>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n(function(scope) {\n    let min = 0;\n    let max = 100;\n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      // Do something when msg arrives\n        const v = Math.floor(((msg.payload - min) / (max - min)) * (100 * 0.46));\n        const d = Math.floor(((msg.payload - min) / (max - min)) * 100);\n        document.getElementById('gauge_'+scope.$id).style.setProperty('--gauge-value', v);\n        document.getElementById('gauge_val_'+scope.$id).innerText = d;\n    }\n   \n  });\n})(scope);\n</script>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":1283.0000343322754,"y":835.0000257492065,"wires":[[]]},{"id":"3e7d17b4939773da","type":"ui_group","name":"BoilKettle","tab":"a74ffe3d.4eb73","order":2,"disp":true,"width":12,"collapse":false},{"id":"34c8e5f1.3881fa","type":"ui_group","name":"test","tab":"85b0e00f12eded88","order":1,"disp":true,"width":6,"collapse":false},{"id":"8db3fac0.99dd48","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"RailPI","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"1","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""},{"id":"2b4c4afbaeb3db97","type":"ui_group","name":"BOIL KETTLE","tab":"b9dd4549c2e0e031","order":1,"disp":true,"width":"6","collapse":false},{"id":"a74ffe3d.4eb73","type":"ui_tab","name":"Brewery","icon":"dashboard","order":1,"disabled":false,"hidden":false},{"id":"85b0e00f12eded88","type":"ui_tab","name":"test 1","icon":"dashboard","disabled":false,"hidden":false},{"id":"b9dd4549c2e0e031","type":"ui_tab","name":"Display","icon":"dashboard","disabled":false,"hidden":false}]

there are a couple of link nodes, you will have to change to your temp sensor info instead of the link node. and the PWM node is set to disable so you can change it to where your heat source gpio is at

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.