How to create a dynamic status LED compound widget?

Good morning everyone

My goal is to create a compound widget which acts as a simple status LED indicator. It should have two bindable properties:

  • value (boolean): Defines whether the LED is turned on
  • colorIndex (Number): Defines the color (enumerated range of colors) of the LED when on

I have a somewhat ugly working version:

<?xml version="1.0" encoding="utf-8"?>
<CompoundWidget id="cwIndicatorLed" width="40" height="40" xmlns="http://www.br-automation.com/iat2015/contentDefinition/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Widgets>
        <Widget xsi:type="widgets.brease.Image" id="imageLed" top="0" left="0" width="40" height="40" zIndex="6" image="LocalMedia/LedGreen.svg" />
        <Widget xsi:type="widgets.brease.NumericInput" id="numColor" top="0" left="0" width="1" height="1" zIndex="0" visible="false" />
  
    </Widgets>
    <Properties>
        <Property xsi:type="LocalProperty" name="value" type="Boolean" defaultValue="false" category="Data" required="true" public="true">
            <Description>LED active</Description>
            <Event name="LedValueChanged">
                <Description>value changed event.</Description>
            </Event>
            <Actions>
                <GetAction name="GetLedValue">
                    <Description>Read value</Description>
                </GetAction>
            </Actions>
        </Property>
        <Property xsi:type="BindableProperty" name="colorIndex" type="Number" category="Data" defaultValue="0" readOnly="false" required="false" > 
            <Description>Color (0:Green, 1: yellow, 2: orange, 3: red, 4: blue, 5: white)</Description>
            <Mappings> 
                <Mapping widget="numColor" property="value"  mode="oneWay" />
            </Mappings>  
        </Property>
        
       
        
    </Properties>

    <Events>

    </Events>

    <Actions>
        
    </Actions>
    <EventBindings>
        <EventBinding id="LedStateChanged"> 
            <Source xsi:type="this.Event" event="LedValueChanged" /> 
            <Operand name="ColorIndex" datatype="ANY_INT"> 
                <ReadTarget xsi:type="widget.Action.Read" widgetRefId="numColor">
                    <Method name="GetValue" />
                </ReadTarget>
            </Operand>
            <EventHandler condition="ColorIndex=0">
                <Action>
                    <ReadTarget xsi:type="this.Action.Read">
                        <Method name="GetLedValue" />
                    </ReadTarget>
                    <Result>
                        <ResultHandler condition="result=false">
                            <Action>
                                <Target xsi:type="widget.Action"  widgetRefId="imageLed">
                                    <Method name="SetImage" image="LocalMedia/LedOff.svg" />
                                </Target>
                            </Action>
                        </ResultHandler>
                        <ResultHandler condition="result=true">
                            <Action>
                                <Target xsi:type="widget.Action"  widgetRefId="imageLed">
                                    <Method name="SetImage" image="LocalMedia/LedGreen.svg" />
                                </Target>
                            </Action>
                        </ResultHandler>
                            
                    </Result>
                </Action>
            </EventHandler>
            <EventHandler condition="ColorIndex=1">
                <Action>
                    <ReadTarget xsi:type="this.Action.Read">
                        <Method name="GetLedValue" />
                    </ReadTarget>
                    <Result>
                        <ResultHandler condition="result=false">
                            <Action>
                                <Target xsi:type="widget.Action"  widgetRefId="imageLed">
                                    <Method name="SetImage" image="LocalMedia/LedOff.svg" />
                                </Target>
                            </Action>
                        </ResultHandler>
                        <ResultHandler condition="result=true">
                            <Action>
                                <Target xsi:type="widget.Action"  widgetRefId="imageLed">
                                    <Method name="SetImage" image="LocalMedia/LedYellow.svg" />
                                </Target>
                            </Action>
                        </ResultHandler>
                            
                    </Result>
                </Action>
            </EventHandler>
            
        </EventBinding> 

    </EventBindings>

</CompoundWidget>

There are several issues with this:

  • It is ugly (i could live with that)
  • It only updates when the “Value” changes. On initial loading of the content, it will display its initial state.
  • It is using the hidden “numColor” widget for storing the color index.

Setting the contents to preCache did come with other problems in our visu.

My questions are:

  1. I would love to create a widget action which handles all the updating. That way i could probably solve the issue of the initial state by calling this action on “OnWidgetsReady” events.
  2. I would love to also declare the “numColor” as a LocalProperty, but i was not able to figure out how to read the value of a local property outside its own “ValueChange” event.

In pseudo code it could be something like:


<?xml version="1.0" encoding="utf-8"?>
<CompoundWidget id="cwIndicatorLed">
	<Widgets>
		<Widget xsi:type="widgets.brease.Image" id="imageLed" top="0" left="0" width="40" height="40" zIndex="6" image="LocalMedia/LedGreen.svg"/>
	</Widgets>
	<Properties>
		<Property xsi:type="LocalProperty" name="value" type="Boolean" defaultValue="false" category="Data" required="true" public="true">
			<Description>LED active</Description>
			<Event name="LedValueChanged" />
			<Actions>
				<GetAction name="GetLedValue" />
			</Actions>
		</Property>
		<Property xsi:type="LocalProperty" name="colorIndex" type="Number" defaultValue="false" category="Data" required="true" public="true">
			<Description>color index</Description>
			<Event name="ColorIndexChanged" />
			<Actions>
				<GetAction name="GetColorIndex" />
			</Actions>
		</Property>
	</Properties>
	<Events>

    </Events>
	<Actions>
		<Action name="UpdateLed">
		Step 1: CALL "GetLedValue"
		Step 2: IF GetLedValue=False THEN <Method name="SetImage" image="LocalMedia/LedOff.svg"/>
		Step 3: ELSE CALL "GetColorIndex"
			Substep 1: IF GetColorIndex=0 THEN <Method name="SetImage" image="LocalMedia/LedGreen.svg"/>
			Substep 2: ELSE IF GetColorIndex=1 THEN <Method name="SetImage" image="LocalMedia/LedYellow.svg"/>
			Substep 3: ELSE IF....	
		</Action>
	</Actions>
	
	<EventBindings>
		<EventBinding id="LedStateChanged">
			<Source xsi:type="this.Event" event="LedValueChanged"/> 
			CALL ACTION UpdateLed    
        </EventBinding>
		<EventBinding id="LedColorChanged">
			<Source xsi:type="this.Event" event="ColorIndexChanged"/> 
			CALL ACTION UpdateLed
        </EventBinding>
		<EventBinding id="WidgetLoaded">
			<Source xsi:type="this.Event" event="OnWidgetsReady"/>  
			CALL ACTION UpdateLed
     </EventBinding>
	</EventBindings>
</CompoundWidget>

Is there an easy way to solve this?

Best Regards and many thanks in advance

Matthias

Hi @Matthias_Kaufmann

One of my idea is to use ImageList and only one parameter on the CompounWidget (the imageIndex). You set the image when the led is off at index 0 then all your color after.

<?xml version="1.0" encoding="utf-8"?>
<CompoundWidget id="compoundwidget_0" width="40" height="40" xmlns="http://www.br-automation.com/iat2015/contentDefinition/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<Widgets>
		<Widget xsi:type="widgets.brease.ImageList" id="Led" top="0" left="0" width="40" height="40" zIndex="0" imageList="['Media/LedGrey.svg', 'Media/LedOrange.svg', 'Media/LedRed.svg', 'Media/LedGreen.svg']" selectedIndex="0" useSVGStyling="false" />
  </Widgets>
	<Properties>
		<Property xsi:type="BindableProperty" name="imageIndex" type="Number" category="Data" defaultValue="0" readOnly="false" required="false" > 
			<Description>Color (0:Grey, 1: Orange, 2: Red, 3: Green)</Description>
			<Mappings> 
				<Mapping widget="Led" property="selectedIndex"  mode="oneWay" />
			</Mappings>  
		</Property>
	</Properties>

	<Events>
	</Events>

	<Actions>
	</Actions>

	<EventBindings>
	</EventBindings>

</CompoundWidget>

In a program (here ST) you use 2 variables (LedOn and ColorIndex) to calculate imageIndex:

VAR
	ColorIndex : USINT;
	LedOn : BOOL;
	ImageIndex : USINT;
END_VAR


PROGRAM _CYCLIC

	ImageIndex := LedOn * (ColorIndex + 1);
	 
END_PROGRAM

Declare “ImageIndex” to the OpcUa and bind it to your compound widget.

Note:
One thing you can check to not use PLC program it’s the EventScript, I didn’t try it yet butI think you can stay with your 2 properties and just put the imageIndex calcul in the value change event of both properties. I will check if I had time!

Hope this could help!

Regards,
Florent

5 Likes

Hi Florent

Thank you for your reply!

Unfortunately i want a solution that only takes a boolean type for state (on/off) and an optional integer type for the color.

But your idea with the image list was excellent, it allowed me to use a much simpler solution.

<?xml version="1.0" encoding="utf-8"?>
<CompoundWidget id="cwIndicatorLed" width="40" height="40" xmlns="http://www.br-automation.com/iat2015/contentDefinition/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Widgets>
        <Widget xsi:type="widgets.brease.ImageList" id="imageListLed" top="0" left="0" width="40" height="40" zIndex="53" imageList="['LocalMedia/LedOff.svg','LocalMedia/LedGreen.svg','LocalMedia/LedYellow.svg','LocalMedia/LedOrange.svg','LocalMedia/LedRed.svg','LocalMedia/LedBlue.svg','LocalMedia/LedWhite.svg']" />
    </Widgets>
    <Properties>
        <Property xsi:type="LocalProperty" name="state" type="Boolean" defaultValue="false" category="Data" required="true" public="true">
            <Description>LED state (On/Off)</Description>
            <Event name="StateChanged">
                <Description>value changed event.</Description>
            </Event>
            <Actions>
                <GetAction name="GetState">
                    <Description>Read state</Description>
                </GetAction>
            </Actions>
        </Property>
        <Property xsi:type="LocalProperty" name="color" type="Number" defaultValue="0" category="Data" required="true" public="true">
            <Description>color (0: green, 1: yellow, 2: orange, 3: red, 4: blue, 5: white)</Description>
            <Event name="ColorChanged">
                <Description>color changed event.</Description>
            </Event>
            <Actions>
                <GetAction name="GetColor">
                    <Description>Read color index</Description>
                </GetAction>
            </Actions>
        </Property>
    </Properties>
    <Events/>
    <Actions/>
    <EventBindings>
        <EventBinding id="LedStateChanged"> 
            <Source xsi:type="this.Event" event="StateChanged" /> 
            <Operand name="ColorIndex" datatype="ANY_INT"> 
                <ReadTarget xsi:type="this.Action.Read" >
                    <Method name="GetColor" />
                </ReadTarget>
            </Operand>
            <Operand name="LedValue" datatype="BOOL"> 
                <ReadTarget xsi:type="this.Action.Read">
                    <Method name="GetState" />
                </ReadTarget>
            </Operand>
            <EventHandler condition="LedValue=true">
                <Action>
                    <Target xsi:type="widget.Action"  widgetRefId="imageListLed">
                        <Method name="SetSelectedIndex" index="=ColorIndex+1" />
                    </Target>
                </Action>
            </EventHandler>
            <EventHandler condition="LedValue=false">
                <Action>
                    <Target xsi:type="widget.Action"  widgetRefId="imageListLed">
                        <Method name="SetSelectedIndex" index="0" />
                    </Target>
                </Action>
            </EventHandler>   
        </EventBinding> 
        <EventBinding id="LedColorChanged"> 
            <Source xsi:type="this.Event" event="ColorChanged" /> 
            <Operand name="ColorIndex" datatype="ANY_INT"> 
                <ReadTarget xsi:type="this.Action.Read" >
                    <Method name="GetColor" />
                </ReadTarget>
            </Operand>
            <Operand name="LedValue" datatype="BOOL"> 
                <ReadTarget xsi:type="this.Action.Read">
                    <Method name="GetState" />
                </ReadTarget>
            </Operand>
            <EventHandler condition="LedValue=true">
                <Action>
                    <Target xsi:type="widget.Action"  widgetRefId="imageListLed">
                        <Method name="SetSelectedIndex" index="=ColorIndex+1" />
                    </Target>
                </Action>
            </EventHandler>
            <EventHandler condition="LedValue=false">
                <Action>
                    <Target xsi:type="widget.Action"  widgetRefId="imageListLed">
                        <Method name="SetSelectedIndex" index="0" />
                    </Target>
                </Action>
            </EventHandler> 
        </EventBinding> 
    </EventBindings>
</CompoundWidget>

This compound widget has the “imageListLed” as its only visible element. Additionaly it has two local properties named “state” and “color”. Each of these properties gets an EventBinding which uses two operands (reading the local properties) and then writes either 0, or “color+1” the “SelectedImageIndex” on the ImageList.

That way, one can either use the “state” property to turn the LED on and off, or use the “color” property to change the color. It’s even possible to use both at the same time as both properties have events to detect changes.