Control Knob suitable for Volume control or similar

Remove those

Add this

ng-model="knobvalue"

Compare with this. I think you still miss the id

 <input type="range"
id="{{'knob_'+$id}}" 
ng-model="knobvalue" 
class="input-knob" 
data-diameter="100" 
data-src="/images/led-knob-30fr.png" 
data-sprites="30"/>

Stand by a moment... I am hanging up on this:

Can I add:

 .knobwrapper{
        width:100%;
        height:100%;
        display:flex;
        justify-content: center;
        align-items: center;
    }

to the template that contains this:
<script src="/js/input-knob.js"></script>

You can. But surround the rule with the <style></style> tags then

Done.. Stand by till I try catch up...

Ok - ID added, deployed... No errors that I can pick up!!

Now be sure that your ui_template where the knob lives has all those

image

And the first part of the magic.

Let's initialize the thing

<script>
(function(scope) {
    scope.inited = false 
    scope.init = function(){
        if($("#knob_"+scope.$id).length){
            actuallyInit()            
        }
        else{
            setTimeout(function(){
                actuallyInit()
            },100)
        }
    }
    
    function actuallyInit(){        
        $("#knob_"+scope.$id).closest("md-card").css('padding','unset')
        if(scope.initialvalue){
            scope.inited = true
            scope.knobvalue = scope.initialvalue          
        }
    }
       
   
})(scope);
</script>

This part of the script will do couple of things.
As the template is introduced to the document it needs to have a bit time to be registered in DOM. We try to be sure all this has happened before we start to manipulate any parameters of it.

$("#knob_"+scope.$id).length

This way you can ask if element exists. (is ready in DOM)

 $("#knob_"+scope.$id).closest("md-card").css('padding','unset')

With this, the padding of the parent element will be removed. This is needed because of the knob likes to be square. Padding forces it to be slightly squeezed.

scope.initialvalue is not declared anywhere yet. But I'll explain usage of it with next step.

Before next step - examine the code and ask questions if something is not understood ..

Confirmed!

Must I add this before or after the existing content? (Or doesn't it matter?)

Still looking at code, so don't jump ahead!!

Usually placed after but does not matter

Ok.. Added...

Looked at code, but this is somewhat beyond my understanding.... The problem I am encountering is the lack of knowledge of the commands within... ie:

I will have to parrot until I know more....

I hope you did understand at least the principle and need of the init phase.

Now lets declare what we want the widget should do for us.

  1. It needs to send value to output, when user moves the knob.
  2. it needs to respond to the input message and turn the knob to correct angle
  3. it needs to pass through the incoming message
  4. it needs to survive the page reload (remember the last value and be rotated to correct angle on wake up)

With all this in mind

The incoming messages

By adding watcher to react when the msg comes

scope.$watch('msg', function(msg) {
        if (msg) {        
            scope.incoming = true
            scope.knobvalue = msg.payload
            if(!scope.initialvalue){
                scope.initialvalue = msg.payload
            }        
        }
  });

Couple of things we do here.

Set flag incoming true in scope. So we know that the current situation and all changes will be based on external input, not by the user moved the knob.

Set the knobvalue to incoming payload. Remember the ng-model="knobvalue" This is how the element and the code are bounded together. This will actually move the knob.

Check if initialvalue is not set in the scope, and if not, then set. This is due the node-red strategy of helping ui widgets to "remember the last state" When the last state exists in server side, it will be sent to the widget and if we can catch it, we are lucky enough to show it. So after reload the page or switching between tabs the widget knows what to show or how to be.
As previously said - the value of it is not set anywhere. now, if the first message after the initialization comes in, we know that this is the message by server to help us out, and we can behave accordingly.

Leaving you now reading this part and going to write the last part - react to user interaction and send out the value.

I understand the principle, but not the specifics!

Agreed!

I understand in principle...

...?... Caught me on this one.... Is this to allow the knob to "self rotate" on display/dashboard should a message be presented to the input of the node?

Last thing then.

 scope.$watch('knobvalue', function(knobvalue) {
        if(!scope.inited){
            return
        }
        if(scope.incoming){
            scope.incoming = false
            return
        }
        if (knobvalue) {
            scope.send({payload:knobvalue})            
        }
    })

By watching the change of knobvalue in scope it takes to know some conditions to decide should the widget send out message.

If the initialization phase is not yet completed - no way.
If the knobvalue is changed by the incoming message - (see previous part) - no way. This would create infinite loop.
And if the knobvalue is real thing (not null or undefined ) it will be sent out.

With this, the magic is done. Complete node . Ready to go.

I think.... no, I know.... I have something not right..... This is the template as it now stands:

<div ng-init="init()" class="knobwrapper">
    
<input type="range" 
id="{{'knob_'+$id}}" 
ng-model="knobvalue" 
class="input-knob" 
data-diameter = "24" 
min="0" 
max="255" 
step="1" 
data-src = "/png/knob70.png" 
data-sprites = "100"
/>
</div>
<script>
(function(scope) {
    scope.inited = false 
    scope.init = function(){
        if($("#knob_"+scope.$id).length){
            actuallyInit()            
        }
        else{
            setTimeout(function(){
                actuallyInit()
            },100)
        }
    }
    
    function actuallyInit(){        
        $("#knob_"+scope.$id).closest("md-card").css('padding','unset')
        if(scope.initialvalue){
            scope.inited = true
            scope.knobvalue = scope.initialvalue          
        }
    }
       
   
})(scope);
scope.$watch('msg', function(msg) {
        if (msg) {        
            scope.incoming = true
            scope.knobvalue = msg.payload
            if(!scope.initialvalue){
                scope.initialvalue = msg.payload
            }        
        }
  });
scope.$watch('knobvalue', function(knobvalue) {
        if(!scope.inited){
            return
        }
        if(scope.incoming){
            scope.incoming = false
            return
        }
        if (knobvalue) {
            scope.send({payload:knobvalue})            
        }
    })
</script>

Onscreen it responds to mouse actions, but no output to a debug node... A message passed to the input does come out the output...

<div ng-init="init()" class="knobwrapper">
    
<input type="range" 
id="{{'knob_'+$id}}" 
ng-model="knobvalue" 
class="input-knob" 
data-diameter = "24" 
min="0" 
max="255" 
step="1" 
data-src = "/png/knob70.png" 
data-sprites = "100"
/>
</div>
<script>
(function(scope) {
    scope.inited = false 
    scope.init = function(){
        if($("#knob_"+scope.$id).length){
            actuallyInit()            
        }
        else{
            setTimeout(function(){
                actuallyInit()
            },100)
        }
    }
    
    function actuallyInit(){        
        $("#knob_"+scope.$id).closest("md-card").css('padding','unset')
        if(scope.initialvalue){           
            scope.knobvalue = scope.initialvalue          
        }
        scope.inited = true
    }
scope.$watch('msg', function(msg) {
        if (msg) {        
            scope.incoming = true
            scope.knobvalue = msg.payload
            if(!scope.initialvalue){
                scope.initialvalue = msg.payload
            }        
        }
  });
scope.$watch('knobvalue', function(knobvalue) {
        if(!scope.inited){
            return
        }
        if(scope.incoming){
            scope.incoming = false
            return
        }
        if (knobvalue) {
            scope.send({payload:knobvalue})            
        }
    })
       
   
})(scope);

</script>

Moved things to correct place. My fault I didn't say where the code of last 2 parts should go.

Oh WOW!!!!

I am gobsmacked!!

Seems like you have created a knob widget. :smiley:
Happy. Well done. :drum: :beers:

1 Like

No Sire!! I disagree entirely!! I prodded, poked, buggered around and... well... You created it!!

I wish there was more that I could say... "Thank you" are such small words.... But I really, really, mean it!!

2 Likes

You have done much more than I did. I shared the knowledge I have. That's fine thing to do. But you took it, and that makes two of us with knowledge. Walking on such route - I can see more and more smart people making the world better place day by day. Thank you to being part of it. :slight_smile:

1 Like

Hey H!

Not at all!

It is people like you that make the world a better place by sharing what you have, whether it be knowledge, time, experience, patience...

All of the above in this case!!

Thank you yet again!!

2 Likes