frangoteam / FUXA

Web-based Process Visualization (SCADA/HMI/Dashboard) software
https://frangoteam.org
MIT License
2.5k stars 750 forks source link

[FEATURE] tag option - scale by script #1197

Open greg9504 opened 1 month ago

greg9504 commented 1 month ago

Describe the feature Tag options for scaling values are currently limited to 3 methods (linear transform, ms to date, ms to time). There are situations where additional transforms must be done to the value before being sent to the client.

Allowing a script to transform the value would allow for support of more hardware without changes to device plugins.

As an example SEW MDX61B* Variable Frequency Drives MODBUS requires parameter values to be encoded in an 8 byte sequence: image

While it is possible to assign a script to an object (button etc) event to transform the value, script events are not available for all object types (sliders).

Describe the solution you'd like In the tag options dialog, add a scale script selection. The user can then select the script to apply the value transform. image

The script must adhere to the simple format of one input variable, and it must return one value, and be configured as SERVER. The format of the output is device plugin dependent. In the case of the MODBUS device, the output would be Node Buffer.

image

image

async function buildSewRampSpeedBuf (value) {
    //movilink parameter channel format:
    // [0x32] management byte = 0x32  
    // [0x00] parameter sub index byte (usually 0x00)
    // [0x2116] parameter index, two bytes 
    // [0x00000000] 4 byte parameter value
    const valBuf = Buffer.alloc(8, 0);
    valBuf.writeUInt8(0x32);
    valBuf.writeUInt16BE(8470, 2);
    valBuf.writeUInt32BE(value, 4);
    console.log(`buildSewRampSpeedBuf returning buffer: ${valBuf.toString('hex')}`);
    return valBuf;
}

Additional context The scale script would be run after any other configured scaling for the tag and is independent of the scale options.

example for MODBUS device in setValue /server/runtime/devices/modbus/index.js

var memaddr = data.tags[sigid].memaddress;
            var offset = parseInt(data.tags[sigid].address) - 1;   // because settings address from 1 to 65536 but communication start from 0
            value = deviceUtils.tagRawCalculator(value, data.tags[sigid]);

            const divVal = convertValue(value, data.tags[sigid].divisor, true);
            var val;
            if (data.tags[sigid].scaleFunction) {
                const script = { id: data.tags[sigid].scaleFunction, name: null, parameters: [{ name: 'value', type: 'value', value: divVal }] };
                try {

                    const bufVal = await runtime.scriptsMgr.runScript(script);
                    if ((bufVal.length % 2) !== 0 ) {
                        logger.error(`'${data.tags[sigid].name}' setValue script error, returned buffer invalid must be mod 2`);
                        return false;
                    }
                    val = [];
                    for (let i = 0; i < bufVal.length;) {
                        val.push(bufVal.readUInt16BE(i));
                        i = i + 2;
                    }
                } catch (error) {
                    logger.error(`'${data.tags[sigid].name}' setValue script error! ${error.toString()}`);
                    return false;
                }

            } else {
                val = datatypes[data.tags[sigid].type].formatter(divVal);
            }

Changes are here https://github.com/greg9504/FUXA/tree/tagscript So far I've only looked at MODBUS and a new plugin I'm writing. If you think this is something worth integrating to FUXA, I can look at making the changes to the other device setValue methods.

*This change alone is not enough to support SEW MOVIDRIVE MDX61B/MOVITRACB devices over modbus... I have a new device plugin for Ethernet/IP that does, I'll be posting details soon. Greg.

unocelli commented 1 month ago

Hi Greg, the script to directly convert the value received via script is a feature that has been missing for so long, it would be great if you could do a PR with this feature. I have a question, converting the input value also needs to convert the output value in the opposite way, right?

greg9504 commented 1 month ago

I have a question, converting the input value also needs to convert the output value in the opposite way, right?

That short answer is that it could be. It would probably be good to allow the option. Just to make sure we are referring to the same thing: You mean applying a script to the value AFTER it is returned from the device (the script would be applied in the device.polling/__updateVarsValue)?

Polling receive value from device -> script(value) -> tagValueCompose(scriptOutput) -> fuxa

I didn't require it for my current use case, but I can add it there.

Greg

greg9504 commented 1 month ago

Oh to do that, I think we need to allow two scripts per tag. Input scale script and output scale script.... or we could require that each script has two input parameters. One parameter is the value, the other is an indication of read/write.

unocelli commented 1 month ago

Yes, I think that is better the first option (two scripts per tag), have the possibility to bind a script for read and a other script for write.

greg9504 commented 1 month ago

OK I've got a bit further, it did end up touching a lot of code. So further testing is needed. Because the scripts are async, I had to await the result. The scripts are applied in device-utils.js tagValueCompose/tagRawCalculator. I chose to make those async. But this meant changes in every plugin to deal with that. Perhaps it would be better to do callScaleScript(...).then in tagValueCompose/tagRawCalculator. That way they would not need to be marked async, and the changes I made in the plugins to handle async tagValueCompose/tagRawCalculator would not be needed.

The script must have the first parameter named "value" of type value. You can add additional parameters to the script, values for those parameters must be filled in in the tag options dialog. This way the same script could be used for many tags, with the parameters used to differentiate. You can use the same script for read/write with different parameters. Parameters are passed to the script as strings, so if you want to use them as numbers a conversion is necessary.

image

This is what the tag options looks like now: image

So far I've only tested with opcua.

unocelli commented 1 month ago

Hi, Great, I will test it as soon as possible