How to create slider in dashboard template

Any idea what I'm doing wrong here?

I am trying to create a lighting automation, by storing values for individual light fixings (as well as scenes / presets for multiple fixtures) in an object in context memory. The object looks like this:

I have created a dashboard template that enumerates the light fixtures shown above (ceiling, wall, bedside, etc.) and it creates a label and slider for each fixture (and shows its value underneath, for testing)

<div ng-repeat="(key, value) in msg.payload">
    <p>{{value.name}}</p>
    <md-slider min="0" max="100" step="5" ng-model="value.state" ng-change="send(msg)"></md-slider>
{{value.state}}
</div>

This creates the slider fine, and the testing value shows 50, which is the correct value set in context memory. However the initial slider value is set to zero, despite {{value.state}} being 50. (Sorry, my screenshot above shows 80, just ignore that.)

If I change the ng-model parameter to add curly brackets {{ }} it stops the slider moving (and doesn't set the initial value to correct amount).

  1. How do I fix this so the slider reflects the correct value in memory, when data is sent to the dashboard template? i.e. at startup

  2. Every time I update the slider, because I'm sending the whole msg object, it will store back ALL of the light fixtures and their metadata. Obviously I don't want this. I just want to send back the actual value that was changed. This would require the template node to send out TWO pieces of data: the name or ID of the fitting, and its value. I have fiddled with ng-change, for example ng-change="send(value.name, value.state)" but that just sends the first item out (the name), not both. How can I do this?

Any other suggestions for how I'm doing this (i.e. "you're doing it all wrong!") will be received with great interest!

Cheers, and happy holidays :slight_smile:

Resolved, in case anyone searches this later:

<div ng-repeat="(key, value) in msg.fixturesettings">
    <div style="display:inline;width:20%">{{value.label}}</div>
    <div style="float:right;width:80%"><md-slider min="0" max="100" step="{{value.resolution}}" ng-model="value.setting.brightness" ng-change="send({action: 'bri', topic: key, brightness: value.setting.brightness})"></md-slider></div>

Thanks for sharing
I'm doing something very similar been playing around with layout and styling of slider

<div>
    <div>LC3 Under Cabinet Lights</div>
    <div class="sliderGrid">
        <div><md-slider min="0" max="100" step="10" ng-model="msg.payload.v1" ng-change="send({topic: 'v1', v: msg.payload.v1})" class="lightSlider"></md-slider></div>
        <div ng-bind-html="msg.payload.v1" class="dimValue"></div>
    </div>
</div>
<div>
    <div>LC4 Table lights</div>
    <div class="sliderGrid">
        <div><md-slider min="0" max="100" step="10" ng-model="msg.payload.v2" ng-change="send({topic: 'v2', v: msg.payload.v2})"  class="lightSlider"></md-slider></div>
        <div ng-bind-html="msg.payload.v2" class="dimValue"></div>
    </div>
</div>
<style>
.sliderGrid{
    display: grid;
    grid-template-columns: 85% 25px;
    margin-left: 25px;
}

.lightSlider {
    float:right;
    width: 100%   
}
.lightSlider .md-thumb:after, .lightSlider .md-track{
    background-color: #FA9900 !important;
    border-color: #FA9900 !important;
}
.lightSlider .md-focus-thumb, .lightSlider .md-focus-ring{
    background-color: #FA9900;
}
.dimValue {
  clear: right; 
  margin: 10px; 0 0 7px;  
}

</style>

Thanks, I'll try out your styling

I've done a bit more since then

I you use md-discrete it should only send once you've finished dragging but you need

.md-track-ticks canvas { display: none; }

This will fill the slider as it's dragged

.dimSlider .md-thumb:after, .dimSlider .md-track-fill{
    background-color: #F9BE1F !important;
    border-color: #F9BE1F !important;
}
1 Like

@iiLaw Lawrence - I tried your code without this bit and two sliders do appear, but I can not move them. All I get in the debug is a payload of "undefined"

I tried in Safari, Chrome and FireFox and all experience the same thing.

This is running on a Mac
macOS 10.14.6
Node-RED v1.0.2
Node.js v12.13.0
Dashboard v2.19.1

ok I've had that and can't remember how to fix

I need to de-angular some of my template code below to recreate the above once I have will post but if your angular is your thing

 <div ng-repeat="controls in msg.payload.controlS | filter:{lbl: '!!'}">
        <div>{{controls.lbl}}</div>
        <div class="{{controls.type}}Grid">
     
            <div ng-If="controls.type == 'dim' "><md-slider id="{{msg.gpId}}C{{$index+1}}" min="0" max="100" step="10" ng-model="controls.v" ng-change="send(action({gpId: msg.gpId, id: $index+1, v: controls.v}))"  md-discrete class="dimSlider"></md-slider></div>
            <div ng-If="controls.type == 'dim' "  id="{{msg.gpId}}C{{$index+1}}v" ng-bind-html="controls.v" class="value"></div>
    
        </div>
    </div> 

Actually I'm not using it for anything. I saw the thread and decided to try your code and when it didn't work I thought it best to report the issue :grin:

@zenofmud Paul thanks test on chrome & FF on win10
And chrome on iOS

<style>
.md-track-ticks canvas { display: none; }
.dimGrid{
    display: grid;
    grid-template-columns: 85% 25px;
    margin-left: 12px;
}
.dimSlider {
    float:left;
    width: 90%   
}
.dimSlider .md-thumb:after, .dimSlider .md-track-fill{
    background-color: #F9BE1F !important;
    border-color: #F9BE1F !important;
}
.value {
  clear: right; 
  margin: 10px; 0 0 7px;  
}
</style>

<div>LC1 - "normal" mode output: continously while sliding </div>
<div class="dimGrid">
    <div><md-slider id="sl-1" min="0" max="100" step="10" ng-model="msg.payload.sv1" ng-change="send({id: 1, payload: msg.payload.sv1})" class="dimSlider"></md-slider></div>
    <div ng-bind-html="msg.payload.sv1" class="value"></div>
</div>
<div>LC2 - "discrete" mode output: only on release</div>
<div class="dimGrid">
    <div><md-slider id="sl-2" md-discrete min="0" max="100" step="10" ng-model="msg.payload.sv2" ng-change="send({id: 2, payload: msg.payload.sv2})" class="dimSlider"></md-slider></div>
    <div ng-bind-html="msg.payload.sv2" class="value"></div>
</div

It's still not working for me in iOS 13.2.2 or on my Mac in Safari, Chrome of FireFox. The top slider doesn't work at all and the bottom slider changes to a teardrop
51 AM
but slides back to 0 as soon as it is released.
12 AM

Ok I've testes on iOS 13.2.3. on Safari & Chrome works. Worked on 13.2.2 as well.
Not sure how I bottom this one out as I don't have a mac.

LC2 example is an example of output: only on release this same behavior as Dashboard slider. When you move focus away it should revert to a ring have to say the UX is bit odd, but I need send on release (e.d slide end).

Top sliders standard Dashboard sliders first continuous/second on release.
Bottom sliders my code.

Hi, Sorry to knock on an old door but this does not work at all for me on chromium from a ui_template (assuming the code came from one because the import of code does not work for me either).

I have been struggling with ui_template ng-slider for days now and have just raised a new topic:
https://discourse.nodered.org/t/ui-template-md-slider-help/37328/2

I get:
image

best regards
oz

Old topic, but I have got my sliders to work within the UI template node.

I have some Shelly RGBW2 led controllers which I control using NodeRED. I have created a UI which controls the different RGB and White channels. So I treat the RGB as mixed and the White channel is a separate light source. I stole the UI of the iOS color picker. It looks nice and works as expected in the Safari (MacOS) browser.

My NodeRED implementation:
Slider Demo

The html angular code:

<style>
.slidecontainer {
	/* font-size: 80%; */
	width: 100%;
}
.slidecontainer input {
	margin-bottom:12px;
}
/* slider bar */
.slider {
  -webkit-appearance: none;
  width: 98%;
  height: 36px;
  border-radius: 18px;
  outline: none;
  padding: 0px 2px;
}

/* per color slides */
.red {
  background: linear-gradient(90deg, rgba(0,{{msg.payload.green}},{{msg.payload.blue}},1) 0%, rgba(255,{{msg.payload.green}},{{msg.payload.blue}},1) 100%);
}
.green {
  background: linear-gradient(90deg, rgba({{msg.payload.red}},0,{{msg.payload.blue}},1) 0%, rgba({{msg.payload.red}},255,{{msg.payload.blue}},1) 100%);
}
.blue {
  background: linear-gradient(90deg, rgba({{msg.payload.red}},{{msg.payload.green}},0,1) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},255,1) 100%);
}
.gain {
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1) 100%);
}
.white {
  background: linear-gradient(90deg, rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},{{msg.payload.gain/100}}) 0%, rgba(253,253,240,1) 100%);
}

/* slider knob */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1);
  border: 2px solid white;
  cursor: pointer;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.white.slider::-webkit-slider-thumb {
  background: rgba(253,253,240,1);
}

/* 
	AAN / UIT knop
	er is een transitie van de knop
	- de knop is 26 pixel breed
	- dus de knop moet van links naar rechts bewegen
	- de hoeveelheid die de knop moet bewegen is de totale breedte min de breedte van de knop
*/

.switch {
  position: relative;
  display: inline-block;
  width: 72px;
  height: 36px;
  appearance: none;
  border: none;
  cursor: pointer;
}
/* remove original */
.switch input { 
  opacity: 0;
  width: 0;
  height: 0;
}

/* button background */
.button {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .1s;
  transition: .1s;
}

/* button */
.button:before {
  -webkit-appearance: none;
  appearance: none;
    position: absolute;
  content: "";
  margin:1px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid white;
  cursor: pointer;
  background: rgba(253,253,240,0.6);
    -webkit-transition: .1s;
  transition: .1s;
}
/* button on */
input + .button {
    border:1px solid white;
}
input:checked + .button {
  background-color: rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},{{msg.payload.gain/100}});
}

/* transition to right */
input:checked + .button:before {
  -webkit-transform: translateX(36px);
  -ms-transform: translateX(36px);
  transform: translateX(36px);
}

/* Rounded input */
.button.round {
  border-radius: 36px;
}
/* layout on off */
.onoff {
  overflow: auto;
}
.onoff label{
  float: right;
}
</style>

<div class="slidecontainer">
    <div class="onoff">
        {{msg.payload.title}}
        <label class="switch"> <input type="checkbox"><span class="button round"></span></label>
    </div>


	Red ({{msg.payload.red}})
	<input type="range" min="0" max="255" value="{{msg.payload.red}}" class="slider red" ng-model="msg.payload.red" ng-mouseup="send(msg)">
	Green ({{msg.payload.green}})
	<input type="range" min="0" max="255" value="{{msg.payload.green}}" class="slider green" ng-model="msg.payload.green" ng-mouseup="send(msg)">
	Blue ({{msg.payload.blue}})
	<input type="range" min="0" max="255" value="{{msg.payload.blue}}" class="slider blue" ng-model="msg.payload.blue" ng-mouseup="send(msg)">
	Gain ({{msg.payload.gain}})
	<input type="range" min="0" max="100" value="{{msg.payload.gain}}" class="slider gain" ng-model="msg.payload.gain" ng-mouseup="send(msg)">

    
    White ({{msg.payload.white}})
	<input type="range" min="0" max="255" value="{{msg.payload.white}}" class="slider white" ng-model="msg.payload.white" ng-mouseup="send(msg)">
</div>

The node-red code:

[
    {
        "id": "2ffcec6.78cdf94",
        "type": "inject",
        "z": "4d2d578e.6cea28",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": "1",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 335,
        "y": 2440,
        "wires": [
            [
                "c9995b07.1f14f"
            ]
        ],
        "l": false
    },
    {
        "id": "c9995b07.1f14f",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "name": "inject",
        "func": "\n// create a random colour\nmsg.payload = { \"title\":\"Demo\",\n                \"red\": Math.floor(Math.random() * (255) ),\n                \"green\": Math.floor(Math.random() * (255) ),\n                \"blue\": Math.floor(Math.random() * (255) ),\n                \"gain\": Math.floor(Math.random() * 101),\n                \"white\": Math.floor(Math.random() * 255)\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 450,
        "y": 2440,
        "wires": [
            [
                "1467f9da.58d96e"
            ]
        ]
    },
    {
        "id": "1467f9da.58d96e",
        "type": "ui_template",
        "z": "4d2d578e.6cea28",
        "group": "fb4c245.8e22cd8",
        "name": "RGB & White sliders",
        "order": 2,
        "width": "0",
        "height": "0",
        "format": "<style>\n.slidecontainer {\n\t/* font-size: 80%; */\n\twidth: 100%;\n}\n.slidecontainer input {\n\tmargin-bottom:12px;\n}\n/* slider bar */\n.slider {\n  -webkit-appearance: none;\n  width: 98%;\n  height: 36px;\n  border-radius: 18px;\n  outline: none;\n  padding: 0px 2px;\n}\n\n/* per color slides */\n.red {\n  background: linear-gradient(90deg, rgba(0,{{msg.payload.green}},{{msg.payload.blue}},1) 0%, rgba(255,{{msg.payload.green}},{{msg.payload.blue}},1) 100%);\n}\n.green {\n  background: linear-gradient(90deg, rgba({{msg.payload.red}},0,{{msg.payload.blue}},1) 0%, rgba({{msg.payload.red}},255,{{msg.payload.blue}},1) 100%);\n}\n.blue {\n  background: linear-gradient(90deg, rgba({{msg.payload.red}},{{msg.payload.green}},0,1) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},255,1) 100%);\n}\n.gain {\n  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1) 100%);\n}\n.white {\n  background: linear-gradient(90deg, rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},{{msg.payload.gain/100}}) 0%, rgba(253,253,240,1) 100%);\n}\n\n/* slider knob */\n.slider::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 32px;\n  height: 32px;\n  border-radius: 50%;\n  background: rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1);\n  border: 2px solid white;\n  cursor: pointer;\n  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n}\n.white.slider::-webkit-slider-thumb {\n  background: rgba(253,253,240,1);\n}\n\n/* \n\tAAN / UIT knop\n\ter is een transitie van de knop\n\t- de knop is 26 pixel breed\n\t- dus de knop moet van links naar rechts bewegen\n\t- de hoeveelheid die de knop moet bewegen is de totale breedte min de breedte van de knop\n*/\n\n.switch {\n  position: relative;\n  display: inline-block;\n  width: 72px;\n  height: 36px;\n  appearance: none;\n  border: none;\n  cursor: pointer;\n}\n/* remove original */\n.switch input { \n  opacity: 0;\n  width: 0;\n  height: 0;\n}\n\n/* button background */\n.button {\n  position: absolute;\n  cursor: pointer;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background-color: #ccc;\n  -webkit-transition: .1s;\n  transition: .1s;\n}\n\n/* button */\n.button:before {\n  -webkit-appearance: none;\n  appearance: none;\n    position: absolute;\n  content: \"\";\n  margin:1px;\n  width: 28px;\n  height: 28px;\n  border-radius: 50%;\n  border: 2px solid white;\n  cursor: pointer;\n  background: rgba(253,253,240,0.6);\n    -webkit-transition: .1s;\n  transition: .1s;\n}\n/* button on */\ninput + .button {\n    border:1px solid white;\n}\ninput:checked + .button {\n  background-color: rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},{{msg.payload.gain/100}});\n}\n\n/* transition to right */\ninput:checked + .button:before {\n  -webkit-transform: translateX(36px);\n  -ms-transform: translateX(36px);\n  transform: translateX(36px);\n}\n\n/* Rounded input */\n.button.round {\n  border-radius: 36px;\n}\n/* layout on off */\n.onoff {\n  overflow: auto;\n}\n.onoff label{\n  float: right;\n}\n</style>\n\n<div class=\"slidecontainer\">\n    <div class=\"onoff\">\n        {{msg.payload.title}}\n        <label class=\"switch\"> <input type=\"checkbox\"><span class=\"button round\"></span></label>\n    </div>\n\n\n\tRed ({{msg.payload.red}})\n\t<input type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.red}}\" class=\"slider red\" ng-model=\"msg.payload.red\" ng-mouseup=\"send(msg)\">\n\tGreen ({{msg.payload.green}})\n\t<input type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.green}}\" class=\"slider green\" ng-model=\"msg.payload.green\" ng-mouseup=\"send(msg)\">\n\tBlue ({{msg.payload.blue}})\n\t<input type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.blue}}\" class=\"slider blue\" ng-model=\"msg.payload.blue\" ng-mouseup=\"send(msg)\">\n\tGain ({{msg.payload.gain}})\n\t<input type=\"range\" min=\"0\" max=\"100\" value=\"{{msg.payload.gain}}\" class=\"slider gain\" ng-model=\"msg.payload.gain\" ng-mouseup=\"send(msg)\">\n\n    \n    White ({{msg.payload.white}})\n\t<input type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.white}}\" class=\"slider white\" ng-model=\"msg.payload.white\" ng-mouseup=\"send(msg)\">\n</div>",
        "storeOutMessages": true,
        "fwdInMessages": false,
        "resendOnRefresh": true,
        "templateScope": "local",
        "x": 660,
        "y": 2440,
        "wires": [
            [
                "69180f62.347ca8"
            ]
        ]
    },
    {
        "id": "fb4c245.8e22cd8",
        "type": "ui_group",
        "name": "Slider demo",
        "tab": "9cb34fc7.ab81b8",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "9cb34fc7.ab81b8",
        "type": "ui_tab",
        "name": "Test",
        "icon": "dashboard",
        "order": 17,
        "disabled": false,
        "hidden": false
    }
]

The original iOS UI looks like this:

3 Likes

Unfortunately none of the above solutions work in my environment. MacOS 10.13 and iOS 11.x
The sliders cannot be moved, the only thing "sliding" is the dasboard screen if you have more than one..

Also the standard slider provided with the latest NR-dashboard works only on MacOS but not on any iOS version. Why is this GUI element so sensitive to the configuration?

@haegar33, I run almost the same setup.

On the server side I have a Pi 3 running Raspbian and node-red. A node-red gives me:

1 Apr 19:40:15 - [info] Node-RED version: v1.2.7
1 Apr 19:40:15 - [info] Node.js version: v12.21.0
1 Apr 19:40:15 - [info] Linux 5.10.17-v7l+ arm LE
...

I have multiple client devices which use Safari, Chrome, Firefox (macOS 10.13, 11.2, iOS 12, 14.4) which all handle the webcode just fine.

Here is the code I use in my live setup (just did a copy-past). Try a test setup to use this.

[
    {
        "id": "60382def.6606cc",
        "type": "mqtt in",
        "z": "4d2d578e.6cea28",
        "name": "Servieskast RGB",
        "topic": "shellies/servieskast/color/0/status",
        "qos": "0",
        "datatype": "auto",
        "broker": "707b233c.d0ad2c",
        "x": 200,
        "y": 1400,
        "wires": [
            [
                "317349a6.05db9e"
            ]
        ]
    },
    {
        "id": "2cef8d55.391302",
        "type": "ui_template",
        "z": "4d2d578e.6cea28",
        "group": "61b475f2.9d3a34",
        "name": "CustomSwitch CSS",
        "order": 1,
        "width": "0",
        "height": "0",
        "format": "<!-- CustomSwitch CSS. See information for correct use of this node. -->\n<style>\n/* SWITCHES */\n\t.CustomSwitchUI {\n\t  height: 36px;\n\t}\n\t.CustomSwitchUI text{\n\t  float: left;\n\t  padding-top: 8px;\n\t}\n\t.CustomSwitchUI label{\n\t  float: right;\n\t}\n\t\n\t.CustomSwitch {\n\t  position: relative;\n\t  display: inline-block;\n\t  width: 72px;\n\t  height: 100%;\n\t  appearance: none;\n\t  border: none;\n\t  cursor: pointer;\n\t}\n\t/* remove original */\n\t.CustomSwitch input { \n\t  opacity: 0;\n\t  width: 0;\n\t  height: 0;\n\t}\n\t\n\t/* button background */\n\t.CustomSwitchButton {\n\t  position: absolute;\n\t  cursor: pointer;\n\t  top: 0;\n\t  left: 0;\n\t  right: 0;\n\t  bottom: 0;\n\t  border-radius: 36px;\n\t  background-color: var(--nr-dashboard-pageBackgroundColor);\n\t  -webkit-transition: .1s;\n\t  transition: .1s;\n\t}\n\t\n\t/* button */\n\t.CustomSwitchButton:before {\n\t  -webkit-appearance: none;\n\t  appearance: none;\n\t    position: absolute;\n\t  content: \"\";\n\t  margin: 1px;\n\t  width: 28px;\n\t  height: 28px;\n\t  border-radius: 50%;\n      background: var(--nr-dashboard-widgetColor); \n      border: 2px solid rgba(230,230,230,1);\n\t  cursor: pointer;\n\t  opacity: .8;\n\t  -webkit-transition: .1s;\n\t  transition: .1s;\n\t}\n\t/* button on */\n\tinput + .CustomSwitchButton {\n\t    border:1px solid var(--nr-dashboard-widgetTextColor);\n\t}\n\tinput:checked + .CustomSwitchButton {\n\t  background-color: var(--nr-dashboard-groupTextColor);\n\t  background-color: yellow;\n      box-shadow: 0 0 3px white;\n\t}\n\t\n\t/* transition to right */\n\tinput:checked + .CustomSwitchButton:before {\n\t  -webkit-transform: translateX(36px);\n\t  -ms-transform: translateX(36px);\n\t  transform: translateX(36px);\n\t}\n/* SLIDERS */\n.CustomSlider {\n  background: linear-gradient(90deg, rgba(34,32,33,1) 0%, rgba(230,230,230,1) 100%);\n}\n/* slider button */\n.CustomSlider::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 32px;\n  height: 32px;\n  border-radius: 50%;\n  background: var(--nr-dashboard-widgetColor); \n  border: 2px solid rgba(230,230,230,1);\n  box-shadow: 0 0 5px white;\n  cursor: pointer;\n}\n.CustomSliderRow {\n\tmargin: 1em 0em;\n\tdisplay:flex;\n  \tflex-wrap: nowrap;\n}\n.CustomSliderRow.packed{\n\tmargin:0;\n}\n/* slider label */\n.CustomSliderRow .CustomLabel {\n\tposition:relative;\n\tfont-size:0.9em;\n\ttop: 12px;\n\tmin-width:6em;\n}\n/* slider alone */\n.CustomSliderRow .CustomSlider {\n  -webkit-appearance: none;\n  height: 36px;\n  border-radius: 18px;\n  outline: none;\n  padding: 0px 2px;\n  width:100%;\n  min-width:10em;\n}\n\n\n</style>\n",
        "storeOutMessages": true,
        "fwdInMessages": false,
        "resendOnRefresh": true,
        "templateScope": "global",
        "x": 440,
        "y": 120,
        "wires": [
            []
        ],
        "icon": "font-awesome/fa-file-code-o"
    },
    {
        "id": "4c7c9b76.c0113c",
        "type": "group",
        "z": "4d2d578e.6cea28",
        "name": "Shelly RGBW2",
        "style": {
            "stroke": "#ffff00",
            "fill": "#ffffbf",
            "fill-opacity": "0.5",
            "label": true
        },
        "nodes": [
            "144679a.8504006",
            "3df03ddc.72fce2",
            "5891ae5d.98592",
            "df1c2e41.9918a",
            "8273a921.cc922",
            "7ca24d43.f10da4",
            "7ae6d122.73ad4",
            "6fd723ba.fff8cc",
            "8457869c.9d4a5",
            "317349a6.05db9e",
            "fb70189a.45f058",
            "fe760ca4.2dd29"
        ],
        "x": 334,
        "y": 1359,
        "w": 852,
        "h": 202
    },
    {
        "id": "144679a.8504006",
        "type": "ui_template",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "group": "efa9c015.d79758",
        "name": "Servieskast RGBW",
        "order": 2,
        "width": "0",
        "height": "0",
        "format": "<style>\n/* slider per color/id */\n#{{msg.CustomUI.id}} .CustomSlider#red {\n  background: linear-gradient(90deg, rgba(0,{{msg.payload.green}},{{msg.payload.blue}},1) 0%, rgba(255,{{msg.payload.green}},{{msg.payload.blue}},1) 100%);\n}\n#{{msg.CustomUI.id}} .CustomSlider#green {\n  background: linear-gradient(90deg, rgba({{msg.payload.red}},0,{{msg.payload.blue}},1) 0%, rgba({{msg.payload.red}},255,{{msg.payload.blue}},1) 100%);\n}\n#{{msg.CustomUI.id}} .CustomSlider#blue {\n  background: linear-gradient(90deg, rgba({{msg.payload.red}},{{msg.payload.green}},0,1) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},255,1) 100%);\n}\n#{{msg.CustomUI.id}} .CustomSlider#gain {\n  background: linear-gradient(90deg, rgba(34,32,33,1) 0%, rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1) 100%);\n}\n\n/* slider button */\n#{{msg.CustomUI.id}} .CustomSlider::-webkit-slider-thumb {\n  background: rgba({{msg.payload.red}},{{msg.payload.green}},{{msg.payload.blue}},1);\n}\n#{{msg.CustomUI.id}} .CustomSlider#white::-webkit-slider-thumb {\n  background: yellow;\n}\n</style>\n\n<div id=\"{{msg.CustomUI.id}}\">\n\n\t<div class=\"CustomSwitchUI\">\n\t\t<text class=\"CustomLabel\">{{msg.CustomUI.label}}</text>\n\t\t<label class=\"CustomSwitch\">\n\t\t\t<input type=\"checkbox\" name=\"{{msg.CustomUI.id}}\" ng-model=\"msg.payload.ison\" ng-change=\"send(msg)\">\n\t\t\t<span class=\"CustomSwitchButton\"></span>\n\t\t</label> \n\t</div>\n\t<div class=\"CustomSliderRow white\">\n\t\t<label class=\"CustomLabel\" id=\"white\">☀️ Dimmer</label>\n\t\t<input class=\"CustomSlider\" id=\"white\" type=\"range\" min=\"0\" max=\"100\" value=\"{{msg.payload.white}}\" ng-model=\"msg.payload.white\" ng-mouseup=\"send(msg)\">\n\t</div>\n    <div class=\"CustomSliderRow\">\n\t\t<label class=\"CustomLabel\" id=\"gain\">🌈 Kleur</label>\n\t\t<input class=\"CustomSlider\" id=\"gain\" type=\"range\" min=\"0\" max=\"100\" value=\"{{msg.payload.gain}}\" ng-model=\"msg.payload.gain\" ng-mouseup=\"send(msg)\">\n\t</div>\n\t<div class=\"CustomSliderRow packed\">\n\t\t<label class=\"CustomLabel\" id=\"red\">Rood</label>\n\t\t<input class=\"CustomSlider\" id=\"red\" type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.red}}\" ng-model=\"msg.payload.red\" ng-mouseup=\"send(msg)\">\n\t</div>\n\t<div class=\"CustomSliderRow packed\">\n\t\t<label class=\"CustomLabel\" id=\"green\">Groen</label>\n\t\t<input class=\"CustomSlider\" id=\"green\" type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.green}}\" ng-model=\"msg.payload.green\" ng-mouseup=\"send(msg)\">\n\t</div>\n\t<div class=\"CustomSliderRow packed\">\n\t\t<label class=\"CustomLabel\" id=\"blue\">Blauw</label>\n\t\t<input class=\"CustomSlider\" id=\"blue\" type=\"range\" min=\"0\" max=\"255\" value=\"{{msg.payload.blue}}\" ng-model=\"msg.payload.blue\" ng-mouseup=\"send(msg)\">\n\t</div>\n\n</div>\n",
        "storeOutMessages": true,
        "fwdInMessages": false,
        "resendOnRefresh": true,
        "templateScope": "local",
        "x": 670,
        "y": 1400,
        "wires": [
            [
                "8457869c.9d4a5"
            ]
        ],
        "icon": "font-awesome/fa-sliders"
    },
    {
        "id": "3df03ddc.72fce2",
        "type": "comment",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "Homekit & RGBW",
        "info": "The HomeKit node has a problem when ever it is deployed. If you have to deploy it more than once (hint: you will have to) then you’ll want to restart node-red for HomeKit to keep working. \n\nReload the browser window\n\n```\ncommand-R\n```\n\n\nEasiest restart:\n\n```\nsudo systemctl restart nodered.service\n```\n\n[source:](https://community.openhab.org/t/my-node-red-homekit-openhab-setup/54577)\n\nRGBW\n\nJe kan de RGB kleuren mixen door de waardes van red, green, blue een waarde te geven van 0...255. Nu kan je met de gain 0...100 bepalen hoe vel de red, green, blue moet branden.\n\nVerder is er nog een white kanaal, dit kanaal stuurt de apparte strook witte leds aan. White kan je instellen van 0...255.\n\nMeer is het niet.\n{\n    \"mode\": \"color\",\n    \"red\": 255,\n    \"green\": 255,\n    \"blue\": 255,\n    \"gain\": 100,\n    \"white\": 255,\n    \"effect\": 0,\n    \"turn\": \"on\"\n}\n\nApple's homekit gebruikt HSL\n\nHSL (hue, saturation, lightness) or HSB (hue, saturation, brightness) are similar.\n\n- Hue (Nederlands: tint), is wat we gewoonlijk 'kleur' noemen, zeg een punt op de servieskast.\n    In het HSV-model wordt de kleur uitgezet op een cirkel, en wordt de plek aangeduid in graden: Hue loopt dus van 0 tot 360 (graden).\n\n- Saturation (Nederlands: verzadiging) dit geeft een hoeveelheid (of felheid) van een kleur aan.\n    Wordt uitgedrukt in procenten, en loopt van 0% (flets, grijs) naar 100% (volle kleur).\n\n- Value of Brightness (Nederlands: intensiteit): staat voor de lichtheid van de kleur.\n    Wordt uitgedrukt in procenten, en loopt van 0% (zwart) naar 100% (wit).",
        "x": 395,
        "y": 1520,
        "wires": [],
        "l": false
    },
    {
        "id": "5891ae5d.98592",
        "type": "homekit-service",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "isParent": true,
        "bridge": "93e1fd.7adf2e",
        "parentService": "",
        "name": "🌈 Servieskast",
        "serviceName": "Lightbulb",
        "topic": "",
        "filter": false,
        "manufacturer": "Default Manufacturer",
        "model": "Default Model",
        "serialNo": "Default Serial Number",
        "cameraConfigVideoProcessor": "ffmpeg",
        "cameraConfigSource": "",
        "cameraConfigStillImageSource": "",
        "cameraConfigMaxStreams": 2,
        "cameraConfigMaxWidth": 1280,
        "cameraConfigMaxHeight": 720,
        "cameraConfigMaxFPS": 10,
        "cameraConfigMaxBitrate": 300,
        "cameraConfigVideoCodec": "libx264",
        "cameraConfigAudioCodec": "libfdk_aac",
        "cameraConfigAudio": false,
        "cameraConfigPacketSize": 1316,
        "cameraConfigVerticalFlip": false,
        "cameraConfigHorizontalFlip": false,
        "cameraConfigMapVideo": "0:0",
        "cameraConfigMapAudio": "0:1",
        "cameraConfigVideoFilter": "scale=1280:720",
        "cameraConfigAdditionalCommandLine": "-tune zerolatency",
        "cameraConfigDebug": false,
        "cameraConfigSnapshotOutput": "disabled",
        "cameraConfigInterfaceName": "",
        "characteristicProperties": "{   \"Brightness\":true,\n    \"Hue\":true,\n    \"Saturation\":true\n}",
        "x": 660,
        "y": 1460,
        "wires": [
            [
                "fb70189a.45f058"
            ],
            []
        ]
    },
    {
        "id": "df1c2e41.9918a",
        "type": "mqtt out",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "",
        "topic": "",
        "qos": "",
        "retain": "",
        "broker": "707b233c.d0ad2c",
        "x": 1110,
        "y": 1400,
        "wires": []
    },
    {
        "id": "8273a921.cc922",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "loop protection",
        "func": "// set topic, loop protection, complete payload\n\n// topic\nmsg.topic = \"shellies/servieskast/color/0/set\"\n\n// fix repeated messages\nlet minimumTimeBetweenMessages = 200;\nlet currentTime=Math.round(Date.now());\nlet previousTime=flow.get('servieskast_loopdetectiontime') || 0;\nlet timeDifference=currentTime-previousTime;\nflow.set('servieskast_loopdetectiontime', currentTime) || 0;\nif ( timeDifference < minimumTimeBetweenMessages){\n    // node.warn(\"loop protection: \"+msg.topic)\n    return null;\n}\n\n// fill msg.payload with (in this order): default -> flow -> new\n// also merge payloads & remove all unneeded keys\nfunction mergePayloads(dest, src) {\n    for(var key in src) { if (key in defaultPayload){ dest[key] = src[key]; } } \n    return dest; \n}\n//   colorpickerbug, when it gets a 0,0,0 it stops sending changes in color signal, use 123, 123, 123 by default\nvar defaultPayload = {\n    \"red\": 123,\n    \"green\": 123,\n    \"blue\": 123,\n    \"gain\": 50,\n    \"white\": 50,\n    \"effect\": 0,\n    \"mode\": \"color\",\n    \"turn\": \"off\"\n}\nvar newPayload = mergePayloads(defaultPayload, flow.get('servieskast_flowPayload'));\nnewPayload = mergePayloads(newPayload, msg.payload);\nflow.set('servieskast_flowPayload', newPayload)\nmsg.payload = flow.get('servieskast_flowPayload')\n\n// correct scale white 255/100\nmsg.payload[\"white\"] = msg.payload.white/100*255;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 955,
        "y": 1400,
        "wires": [
            [
                "df1c2e41.9918a"
            ]
        ],
        "icon": "font-awesome/fa-paperclip",
        "l": false
    },
    {
        "id": "7ca24d43.f10da4",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "prep homekit",
        "func": "// convert RGB to HSL for Homekit\nfunction rgbToHsl(r, g, b) {\n  r /= 255, g /= 255, b /= 255;\n\n  var max = Math.max(r, g, b), min = Math.min(r, g, b);\n  var h, s, l = (max + min) / 2;\n\n  if (max == min) {\n    h = s = 0; // achromatic\n  } else {\n    var d = max - min;\n    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n    switch (max) {\n      case r: h = (g - b) / d + (g < b ? 6 : 0); break;\n      case g: h = (b - r) / d + 2; break;\n      case b: h = (r - g) / d + 4; break;\n    }\n    h /= 6;\n  }\n  // rounding to whole numbers prevents decline due to decimal rounding in loops.\n  h = Math.round(h*360);\n  s = Math.round(s*100);\n  l = Math.round(l*100);\n  return [ h, s, l ];\n}\n\nfunction rgbToHsv(r, g, b) {\n  // Assumes r, g, and b are contained in the set [0, 255] and\n  // returns h, s, and v in the set [0, 1].\n  r /= 255, g /= 255, b /= 255;\n\n  var max = Math.max(r, g, b), min = Math.min(r, g, b);\n  var h, s, v = max;\n\n  var d = max - min;\n  s = max == 0 ? 0 : d / max;\n\n  if (max == min) {\n    h = 0; // achromatic\n  } else {\n    switch (max) {\n      case r: h = (g - b) / d + (g < b ? 6 : 0); break;\n      case g: h = (b - r) / d + 2; break;\n      case b: h = (r - g) / d + 4; break;\n    }\n\n    h /= 6;\n  }\n  h = h*360;\n  s = s*100;\n  v = v*100;\n  \n  return [ h, s, v ];\n}\n\n// set hsl\nlet hsl = rgbToHsv( msg.payload.red,\n                    msg.payload.green,\n                    msg.payload.blue\n                    )\n\n// brightness of 100 makes the RGB go white\nif ( msg.payload.gain >= 100 ){\n    msg.payload.gain = 99;\n}\n\n// var homekitMsg = { payload:{    \"Hue\": hsl[0],\n//                                 \"Saturation\": hsl[1],\n//                                 \"Brightness\": msg.payload.gain\n//                             }\n//                 };\nvar homekitMsg = { payload:{    \"Hue\": hsl[0],\n                                \"Saturation\": hsl[1],\n                                \"Brightness\": hsl[2]\n                            }\n                };\n\nhomekitMsg.payload[\"On\"]=msg.payload.ison\n\nreturn homekitMsg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 515,
        "y": 1460,
        "wires": [
            [
                "5891ae5d.98592"
            ]
        ],
        "icon": "node-red-contrib-homekit-bridged/homekit.png",
        "l": false
    },
    {
        "id": "7ae6d122.73ad4",
        "type": "homekit-service",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "isParent": true,
        "bridge": "93e1fd.7adf2e",
        "parentService": "",
        "name": "☀️ Servieskast Wit",
        "serviceName": "Lightbulb",
        "topic": "",
        "filter": false,
        "manufacturer": "Default Manufacturer",
        "model": "Default Model",
        "serialNo": "Default Serial Number",
        "cameraConfigVideoProcessor": "ffmpeg",
        "cameraConfigSource": "",
        "cameraConfigStillImageSource": "",
        "cameraConfigMaxStreams": 2,
        "cameraConfigMaxWidth": 1280,
        "cameraConfigMaxHeight": 720,
        "cameraConfigMaxFPS": 10,
        "cameraConfigMaxBitrate": 300,
        "cameraConfigVideoCodec": "libx264",
        "cameraConfigAudioCodec": "libfdk_aac",
        "cameraConfigAudio": false,
        "cameraConfigPacketSize": 1316,
        "cameraConfigVerticalFlip": false,
        "cameraConfigHorizontalFlip": false,
        "cameraConfigMapVideo": "0:0",
        "cameraConfigMapAudio": "0:1",
        "cameraConfigVideoFilter": "scale=1280:720",
        "cameraConfigAdditionalCommandLine": "-tune zerolatency",
        "cameraConfigDebug": false,
        "cameraConfigSnapshotOutput": "disabled",
        "cameraConfigInterfaceName": "",
        "characteristicProperties": "{   \n    \"Brightness\":true\n}",
        "x": 670,
        "y": 1520,
        "wires": [
            [
                "fe760ca4.2dd29"
            ],
            []
        ]
    },
    {
        "id": "6fd723ba.fff8cc",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "prep homekit",
        "func": "// convert white to Brightness\nvar brightness = msg.payload.white;\nvar homekitMsg = { payload:{ \"Brightness\": brightness,\n                                \"On\": msg.payload.ison }};\nreturn homekitMsg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 515,
        "y": 1520,
        "wires": [
            [
                "7ae6d122.73ad4"
            ]
        ],
        "icon": "node-red-contrib-homekit-bridged/homekit.png",
        "l": false
    },
    {
        "id": "8457869c.9d4a5",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "post UI",
        "func": "// translate ison\nif(msg.payload.ison === true){\n    msg.payload.turn = \"on\"\n}else{\n    msg.payload.turn = \"off\"\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 815,
        "y": 1400,
        "wires": [
            [
                "8273a921.cc922"
            ]
        ],
        "icon": "font-awesome/fa-gears",
        "l": false
    },
    {
        "id": "317349a6.05db9e",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "prep",
        "func": "// prep payload, convert from JSON\nif( msg.payload !== undefined){\n    msg.payload=JSON.parse(msg.payload)\n}else{\n    msg.payload= {};\n}\n\n// msg.CustomUI.id\nmsg.CustomUI ={ \"id\":\"servieskast\",\n                \"label\":\"Servieskast\"\n}\n\nmsg.payload[\"white\"] = msg.payload.white/255*100;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 375,
        "y": 1400,
        "wires": [
            [
                "7ca24d43.f10da4",
                "6fd723ba.fff8cc",
                "144679a.8504006"
            ]
        ],
        "l": false
    },
    {
        "id": "fb70189a.45f058",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "post HK rgb",
        "func": "// white\n// gain\n// red\n// green\n// blue\n// turn [on,off]\n\n// set variables\nvar h = msg.payload.Hue || flow.get('servieskast_hue');\nvar s = msg.payload.Saturation || flow.get('servieskast_saturation');\nvar b = msg.payload.Brightness || flow.get('servieskast_brightness');\nflow.set('servieskast_hue', h);\nflow.set('servieskast_saturation', s);\nflow.set('servieskast_brightness', b);\n\n// brightness (cleanup needed?)\nif(Number.isFinite(msg.payload[\"Brightness\"])){\n    brightness = msg.payload[\"Brightness\"];\n    if ( brightness >= 100 ){\n        brightness = 99;\n    }\n    msg.payload[\"gain\"] = brightness;\n    delete msg.payload[\"Brightness\"];\n}\n\nfunction hsvToRgb(h, s, v) {\n  // hsv == hsb\n  // modded from https://gist.github.com/mjackson/5311256\n  // expect range [0, 1]\n  h = h/360;\n  s = s/100;\n  v = v/100;\n  var r, g, b;\n\n  var i = Math.floor(h * 6);\n  var f = h * 6 - i;\n  var p = v * (1 - s);\n  var q = v * (1 - f * s);\n  var t = v * (1 - (1 - f) * s);\n\n  switch (i % 6) {\n    case 0: r = v, g = t, b = p; break;\n    case 1: r = q, g = v, b = p; break;\n    case 2: r = p, g = v, b = t; break;\n    case 3: r = p, g = q, b = v; break;\n    case 4: r = t, g = p, b = v; break;\n    case 5: r = v, g = p, b = q; break;\n  }\n\n  return [r * 255, g * 255, b * 255 ];\n}\n\nvar rgb = hsvToRgb(h, s, b);\nmsg.payload[\"red\"] = rgb[0];\nmsg.payload[\"green\"] = rgb[1];\nmsg.payload[\"blue\"] = rgb[2];\n\n// set on/off\nif (msg.payload.On === true){\n    msg.payload[\"turn\"]=\"on\"\n    delete msg.payload.On;\n}\nif (msg.payload.On === false){\n    msg.payload[\"turn\"]=\"off\"\n    delete msg.payload.On;\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 815,
        "y": 1460,
        "wires": [
            [
                "8273a921.cc922"
            ]
        ],
        "icon": "node-red-contrib-color-convert/color-convert.png",
        "l": false
    },
    {
        "id": "fe760ca4.2dd29",
        "type": "function",
        "z": "4d2d578e.6cea28",
        "g": "4c7c9b76.c0113c",
        "name": "post HK white",
        "func": "// white\n// gain\n// red\n// green\n// blue\n// turn [on,off]\n\n// set variables\nmsg.payload[\"white\"] = msg.payload.Brightness;\n\n// set on/off\nif (msg.payload.On === true){\n    msg.payload[\"turn\"]=\"on\"\n    delete msg.payload.On;\n}\nif (msg.payload.On === false){\n    msg.payload[\"turn\"]=\"off\"\n    delete msg.payload.On;\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 815,
        "y": 1520,
        "wires": [
            [
                "8273a921.cc922"
            ]
        ],
        "icon": "font-awesome/fa-adjust",
        "l": false
    },
    {
        "id": "efa9c015.d79758",
        "type": "ui_group",
        "name": "Kleuren",
        "tab": "f529ddcc.a47f8",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": true
    },
    {
        "id": "93e1fd.7adf2e",
        "type": "homekit-bridge",
        "bridgeName": "Koekoek Home",
        "pinCode": "111-11-111",
        "port": "",
        "allowInsecureRequest": false,
        "manufacturer": "Koekoek",
        "model": "Model",
        "serialNo": "Serial Number",
        "customMdnsConfig": false,
        "mdnsMulticast": true,
        "mdnsInterface": "",
        "mdnsPort": "",
        "mdnsIp": "",
        "mdnsTtl": "",
        "mdnsLoopback": true,
        "mdnsReuseAddr": true,
        "allowMessagePassthrough": true
    },
    {
        "id": "f529ddcc.a47f8",
        "type": "ui_tab",
        "name": "Koekoek",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    },
    {
        "id": "707b233c.d0ad2c",
        "type": "mqtt-broker",
        "name": "KoekoekPi",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": false,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthRetain": "false",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closeRetain": "false",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willRetain": "false",
        "willPayload": ""
    },
    {
        "id": "61b475f2.9d3a34",
        "type": "ui_group",
        "name": "Lampen",
        "tab": "f529ddcc.a47f8",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    }
]

Hartelijk bedankt :wink: This a rather complex example embedded with some Shelly MQTT messages. I will try to extract the essence to test a simple slider movement.

actual this example flow works!

https://flows.nodered.org/flow/4b2ebe373bcac34e6b18be998bb47f93

Its even working on my very old iPad2. I suspect this is pure HTML5 code without any fancy javscript libs?