Dear Community User! We have started the migration process.
This community is now in READ ONLY mode.
Read more: Important information on the platform change.

cancel
Showing results for 
Search instead for 
Did you mean: 
SOLVED

Changing Image Path based on Variable

Changing Image Path based on Variable

akrosis
Established Member

Hello all,

We are working on a project in which we'll have 300-500 images. So simply hard coding it to hmi or PLC is not our best option. What we thought is to have a string variable, and PLC will write the changeble part of the Image to that variable. 

For example,  if that variable is xxx1.png, image should change to "pics/custom/xxx1.png".

I'm trying to do it using local script but I'm not completely sure what I'm doing and I'd definetly use some help about it. I also couldn't figure it out if we have to use composites or just local script and image changer would work. 

Normally I wouldn't ask this but if someone provide some example codes it would be really great.  You can also see some codes I've tried to do in below, which could be completely faulty. 

Thank you very much for your support.

 

 module.run = function (self) {
 
// Get reference to Image Changer widget
let ic = shmi.getControlByName("iq-image-changer");

// Get variable value
let itemValue = shmi.getInputValue('inputDeneme');

// Define image paths object
let images = {
  "1": "pics/custom/parca_Yan_isaretli.png",
  "2": "pics/custom/parca_ust_isaretli.png"  
};

// Set image source path
let imagePath = images[itemValue];

// Call setCompositePlaceholders to update image source
shmi.setCompositePlaceholders(ic, {
  "imageSource": imagePath
});
9 REPLIES 9

Sgilk
Frequent Contributor

Hi @akrosis ,

Here is an example project where you can change the image based on a dropdown menu. You will simply write to the item in your PLC project instead of via the dropdown select.

Here is the local-script that handles the image changing, as well as the entire project. Please let me know if you have any questions.

(function () {

    /**
     * replace module name with a custom name for the local-script.
     *
     * All local-script should be attached to the "custom.ls" package.
     * If more than one script is required for an application, a common root package
     * should be created (e.g. "custom.ls.customerName.*").
     */

    var MODULE_NAME = "image-changer-script",
        ENABLE_LOGGING = false,
        RECORD_LOG = false,
        logger = shmi.requires("visuals.tools.logging").createLogger(MODULE_NAME, ENABLE_LOGGING, RECORD_LOG),
        fLog = logger.fLog,
        log = logger.log,
        module = shmi.pkg( MODULE_NAME );

    // MODULE CODE - START

    /* private variables */

    basePath = "pics/custom/";
    endPath = ".png"

    /* private functions */    
    
    function updateImage(val, control) {
        // update control image
        control.vars.imageEl.src=basePath + val + endPath;    
    }

    /**
     * Implements local-script run function.
     *
     * This function will be called each time a local-script will be enabled.
     *
     * @param {LocalScript} self instance reference of local-script control
     */
    module.run = function (self) {
        //Place your Code here
        const im = shmi.requires("visuals.session.ItemManager");
        self.vars = self.vars || {};
        
        self.vars.cancelable = shmi.onReady({
            controls: {
                demoImageChanger: ".DemoImageChanger"
            }
        }, function(resolved) {
            const demoImageChanger = resolved.controls.demoImageChanger;

            // subscribe item
            self.vars.token = im.subscribe(["virtual:imagePath"], function(name, val, type) {
                // This callback function is called automatically, whenever the value of the item changes
                // check for value of subscribed item and update image
                if (typeof val === "string") {
                    updateImage(val, demoImageChanger);
                }
            });
        });

        /* called when this local-script is disabled */
        self.onDisable = function () {
            self.run = false; /* from original .onDisable function of LocalScript control */
            
            // unsubscribe item - this is very important - otherwise memory leaks might occur
            if (self.vars.token) {
                self.vars.token.unlisten();
            }

            // cancel shmi.onReady to ensure there will nothing be running in the background anymore if the widgets have not been found
            if (self.vars.cancelable) {
                self.vars.cancelable.cancel();
            }
        };
    };


    // MODULE CODE - END

    fLog("module loaded");
})();

 

CodeShepherd
Community Moderator
Community Moderator

Moved to coresponding sub forum Smart HMI - WebIQ Designer and Server@akrosis Or are you using a different HMI tool?

webiq-sk
Frequent Contributor

Just a quick note: I don't know if I understand you correctly, but from my understanding you'd rather like to have 500 items permanently subcribed from the HMI just to change images instead of manually placing images once through WebIQ Designer? Performance-wise that would be a bad solution. Can you please elaborate on this?

Please also note that WebIQ has an Image Changer widget that can show different images based on item values.

Please also see @Sgilk 's example which uses the recommended shmi.onReady function. getControlByName and getInputValue are not the correct methods to use here, actually they are forbidden to use because they are not documented and might be changed or removed at any time. Only use documented (i.e. public) methods when writing code.

akrosis
Established Member

Hello All,

First of all, thank you @Sgilk for the example, that looks great. I'll try that asap.

@webiq-sk Basically, we have different images for every part. And there are more then 300 parts. And there may be new parts coming once in a while. So, adding all of these to image changer widget is not feasible. What we thought is since we get part ID's in PLC, we can send this to HMI with a variable, and our widget can display that image with that name.

Customer will put images in releated folder, so if image is in the file, we can call it with part ID, since the image name will be the same. So this might work. 

Only thing I don't know is, does the script updates when the variable from PLC changes? Because variables are the only things that will change, operator won't choose anything, and image should change according to that variable. Do you know if scripts change like this, or is there a function or something else we can use? 

Thank you.

webiq-sk
Frequent Contributor

Thanks for providing more information. Please note that you should not cause permanent additional load just to save time when updating the HMI every now and then. There might be other ways to somewhat automate this so that the HMI has the correct information without having so many item subscriptions (which is not an issue for WebIQ, but will put more load on the hardware). I still haven't understood exactly what you're trying to do there because I assume you have many images on different parts of the HMI, but not all might be visible at any time probably which means the code would have to be adapted/duplicated etc.

You should familiarize yourself how WebIQ uses JavaScript for its functionality, e.g. by looking at the Scripting Demo in detail. Otherwise you'll have to ask a lot of questions to get your code working and prevent issues. Please see the documentation for the subscribe method specifically. You also need to understand that you have to unsubcribe when your LocalScript is unloaded, e.g. when used inside a screen view. Otherwise your code will cause memory leaks which might crash the browser in hours, days, weeks or months.

 

akrosis
Established Member

exampleHMI.png

Ok maybe I can explain differently because I think you are mentioning something entirely different. 

As you can see from attached image, there is an image area. We'd like to change this image, with every new part that is being produced in station. Our issue is, station produces more then 300 different parts. And, new ones can be introduced any time.

So our solution is, we can provide "path" to image changer via a variable (ex: type number) from PLC. That way, we don't have to add 300+ items to image changer. I hope that makes it clear.

 So @Sgilk's example already provides a solution. My other question is, does local scripts runs when a variable changes? Or does it do when page changes? Since screen will be the same, we'd like to contionusly run the script, so when value from PLC changes, image will also change. 

I've checked every example in your download area, unfortunately I couldn't find anything releated to this. I'm happy to check if there are some more examples in different places, or same examples if you think I missed something.

webiq-sk
Frequent Contributor

I suggest learning a bit more JavaScript, callbacks and asynchronous code to understand what it does, otherwise memory leaks and unexpected behavior might occur if you write your own code or modify it. The scripting demo (you can download right from inside WebIQ Designer) also has an example on item subscription that is well documented in the source code.

LocalScripts are passed unmodified to the browser, i.e. they do exactly what you include there - there's no parsing happening. LocalScripts - like any other JavaScript on any other website - are only executed once. If you use however callback functions (asynchronous code) these will be running until the callback is called or in this case whenever the item changes. This is also the reason you have to understand how asynchronous JavaScript code works if you want to use it:

doSomething();
// line1
doSomeOtherThing(function() {
    // line 2
});
// line 3

The execution order in JavaScript here is usually "line1", "line3" and then if the callback is called "line2", except when the call to "line2" is very fast so it is executed before "line3" is reached - but this might vary depending on the device used and current system load.

akrosis
Established Member

I understand your concerns. I'll definetly look in callback functions and possible problems, in detail. I'll write the result or issues afterwards. 

Thank you for your support.

akrosis
Established Member

Hello All,

We have a working function, as mentioned above. I'm sharing it if someone needs it. What it does is basically when PLC changes "Resim" variable, this function will run. Based on "Resim" and "UnitNo" variables, it'll change the path of the image which changes the image on screen.

 

 

(function () {

    /**
     * replace module name with a custom name for the local-script.
     *
     * All local-script should be attached to the "custom.ls" package.
     * If more than one script is required for an application, a common root package
     * should be created (e.g. "custom.ls.customerName.*").
     */

    var MODULE_NAME = "imageChangeScript",
        ENABLE_LOGGING = false,
        RECORD_LOG = false,
        logger = shmi.requires("visuals.tools.logging").createLogger(MODULE_NAME, ENABLE_LOGGING, RECORD_LOG),
        fLog = logger.fLog,
        log = logger.log,
        module = shmi.pkg( MODULE_NAME );

    // MODULE CODE - START

    /* private variables */

    
    /* private functions */
    function updateImage(val,basePath, control) {
        // update control image, 
        // basePath_first = "pics/custom/parcaGoruntuleri/"; // for offline tryouts
        // uniteNo = "virtual:deneme_uniteNo" 
        // basePath = basePath_first + uniteNo;
        endPath = ".jpg"  
        control.vars.imageEl.src=basePath + val + endPath;  
        fLog("UpdateImage Mevcut basepath: " + control.vars.imageEl.src);
	}		
    /* private functions */

    /**
     * Implements local-script run function.
     *
     * This function will be called each time a local-script will be enabled. dene
     *
     * @param {LocalScript} self instance reference of local-script control
     */
    module.run = function (self) {

        //Place your Code here
        const im = shmi.requires("visuals.session.ItemManager");
        self.vars = self.vars || {};
        self.vars.cancelable = shmi.onReady({
            controls: {
                mainChangeImage: ".image_mainScreen"
            }
    }, function(resolved) {
        const mainChangeImage = resolved.controls.mainChangeImage;
         //self.vars.unitToken = im.subscribe(["virtual:deneme_uniteNo"], function(name, unitVal, type) { // for offline tryouts
         self.vars.unitToken = im.subscribe(["unitNo"], function(name, unitVal, type) {
         // This callback function is called automatically, whenever the value of the item changes
            fLog("Unit No Mevcut: " + unitVal);
            self.vars.unitNoYeni = unitVal; // store the unit value in self.vars
        });
    
        //self.vars.imageToken = im.subscribe(["virtual:imageChange_degisken"], function(name, val, type) { // for offline tryouts
        self.vars.token = im.subscribe(["Resim"], function(name, val, type) {
            // This callback function is called automatically, whenever the value of the item changes
            // check for value of subscribed item and update image
            if (typeof val === "string") {
                // get unit value
                const unitNoYeni = self.vars.unitNoYeni;
                fLog("OP bilgi unitNoYeni mevcut: " + unitNoYeni);
                fLog("OP bilgi Resim Degiskeni mevcut: " + val);
                // construct basePath, check PLC inits to 0_ and display initilization image
                if (val === '0_') {
                    const basePath = "pics/custom/parcaGoruntuleri/";
                    fLog("OP bilgi basePath val === 0_: " + basePath); 
                    updateImage(val, basePath, mainChangeImage);
                }
                if (val != '0_') {
                    const basePath = "pics/custom/parcaGoruntuleri/" + unitNoYeni + "/"; 
                    fLog("OP bilgi basePath val != 0_: " + basePath); 
                    updateImage(val, basePath, mainChangeImage);
                }
                //updateImage(val, basePath, mainChangeImage);
                //fLog("OP bilgi basePath: " + basePath);
            }
            });
        });

/* called when this local-script is disabled */
        self.onDisable = function () {
            self.run = false; /* from original .onDisable function of LocalScript control */
            // unsubscribe item - this is very important - otherwise memory leaks might occur
            if (self.vars.token) {
                self.vars.token.unlisten();
            }
             if (self.vars.unitToken) {
                self.vars.unitToken.unlisten();
             }   
            // cancel shmi.onReady to ensure there will nothing be running in the background anymore if the widgets have not been found
            if (self.vars.cancelable) {
                self.vars.cancelable.cancel();
            }
        };
    };
    // MODULE CODE - END

    fLog("module loaded");
})();

 

 

Icon--AD-black-48x48Icon--address-consumer-data-black-48x48Icon--appointment-black-48x48Icon--back-left-black-48x48Icon--calendar-black-48x48Icon--center-alignedIcon--Checkbox-checkIcon--clock-black-48x48Icon--close-black-48x48Icon--compare-black-48x48Icon--confirmation-black-48x48Icon--dealer-details-black-48x48Icon--delete-black-48x48Icon--delivery-black-48x48Icon--down-black-48x48Icon--download-black-48x48Ic-OverlayAlertIcon--externallink-black-48x48Icon-Filledforward-right_adjustedIcon--grid-view-black-48x48IC_gd_Check-Circle170821_Icons_Community170823_Bosch_Icons170823_Bosch_Icons170821_Icons_CommunityIC-logout170821_Icons_Community170825_Bosch_Icons170821_Icons_CommunityIC-shopping-cart2170821_Icons_CommunityIC-upIC_UserIcon--imageIcon--info-i-black-48x48Icon--left-alignedIcon--Less-minimize-black-48x48Icon-FilledIcon--List-Check-grennIcon--List-Check-blackIcon--List-Cross-blackIcon--list-view-mobile-black-48x48Icon--list-view-black-48x48Icon--More-Maximize-black-48x48Icon--my-product-black-48x48Icon--newsletter-black-48x48Icon--payment-black-48x48Icon--print-black-48x48Icon--promotion-black-48x48Icon--registration-black-48x48Icon--Reset-black-48x48Icon--right-alignedshare-circle1Icon--share-black-48x48Icon--shopping-bag-black-48x48Icon-shopping-cartIcon--start-play-black-48x48Icon--store-locator-black-48x48Ic-OverlayAlertIcon--summary-black-48x48tumblrIcon-FilledvineIc-OverlayAlertwhishlist