SVGHMI: Added relative changes of HMI value from widgets. svghmi
authorEdouard Tisserant
Thu, 24 Oct 2019 10:02:07 +0200
branchsvghmi
changeset 2803 f48121cf31b6
parent 2802 64e6f73b9859
child 2804 a6be58a1a8b7
SVGHMI: Added relative changes of HMI value from widgets.
svghmi/gen_index_xhtml.xslt
svghmi/svghmi.js
--- a/svghmi/gen_index_xhtml.xslt	Tue Oct 22 22:58:55 2019 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Thu Oct 24 10:02:07 2019 +0200
@@ -320,323 +320,357 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>function dispatch_value(index, value) {
 </xsl:text>
     <xsl:text>    let widgets = subscribers[index];
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    // TODO : value cache
+    <xsl:text>    if(widgets.size &gt; 0) {
+</xsl:text>
+    <xsl:text>        for(let widget of widgets){
+</xsl:text>
+    <xsl:text>            let idxidx = widget.indexes.indexOf(index);
+</xsl:text>
+    <xsl:text>            if(idxidx == -1){
+</xsl:text>
+    <xsl:text>                throw new Error("Dispatching to widget not interested, should not happen.");
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            let d = widget.dispatch;
+</xsl:text>
+    <xsl:text>            if(typeof(d) == "function" &amp;&amp; idxidx == 0){
+</xsl:text>
+    <xsl:text>                return d.call(widget,value);
+</xsl:text>
+    <xsl:text>            }else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
+</xsl:text>
+    <xsl:text>                d[idxidx].call(widget,value);
+</xsl:text>
+    <xsl:text>            }/* else dispatch_0, ..., dispatch_n ? */
+</xsl:text>
+    <xsl:text>            /*else {
+</xsl:text>
+    <xsl:text>                throw new Error("Dunno how to dispatch to widget at index = " + index);
+</xsl:text>
+    <xsl:text>            }*/
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    cache[index] = value;
 </xsl:text>
     <xsl:text>    
 </xsl:text>
-    <xsl:text>    if(widgets.size &gt; 0) {
-</xsl:text>
-    <xsl:text>        for(let widget of widgets){
-</xsl:text>
-    <xsl:text>            let idxidx = widget.indexes.indexOf(index);
-</xsl:text>
-    <xsl:text>            if(idxidx == -1){
-</xsl:text>
-    <xsl:text>                throw new Error("Dispatching to widget not interested, should not happen.");
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function init_widgets() {
+</xsl:text>
+    <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
+</xsl:text>
+    <xsl:text>        let widget = hmi_widgets[id];
+</xsl:text>
+    <xsl:text>        let init = widget.init;
+</xsl:text>
+    <xsl:text>        if(typeof(init) == "function"){
+</xsl:text>
+    <xsl:text>            return init.call(widget);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    });
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>// Open WebSocket to relative "/ws" address
+</xsl:text>
+    <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
+</xsl:text>
+    <xsl:text>ws.binaryType = 'arraybuffer';
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>const dvgetters = {
+</xsl:text>
+    <xsl:text>    INT: [DataView.prototype.getInt16, 2],
+</xsl:text>
+    <xsl:text>    BOOL: [DataView.prototype.getInt8, 1]
+</xsl:text>
+    <xsl:text>    /* TODO */
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>// Register message reception handler 
+</xsl:text>
+    <xsl:text>ws.onmessage = function (evt) {
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    let data = evt.data;
+</xsl:text>
+    <xsl:text>    let dv = new DataView(data);
+</xsl:text>
+    <xsl:text>    let i = 0;
+</xsl:text>
+    <xsl:text>    //console.log("Recv something.");
+</xsl:text>
+    <xsl:text>    try {
+</xsl:text>
+    <xsl:text>        for(let hash_int of hmi_hash) {
+</xsl:text>
+    <xsl:text>            if(hash_int != dv.getUint8(i)){
+</xsl:text>
+    <xsl:text>                throw new Error("Hash doesn't match");
+</xsl:text>
+    <xsl:text>            };
+</xsl:text>
+    <xsl:text>            i++;
+</xsl:text>
+    <xsl:text>        };
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //console.log("Recv something GOOD.");
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        while(i &lt; data.byteLength){
+</xsl:text>
+    <xsl:text>            let index = dv.getUint32(i, true);
+</xsl:text>
+    <xsl:text>            //console.log("Recv something index is "+index);
+</xsl:text>
+    <xsl:text>            i += 4;
+</xsl:text>
+    <xsl:text>            let iectype = hmitree_types[index];
+</xsl:text>
+    <xsl:text>            if(iectype != undefined){
+</xsl:text>
+    <xsl:text>                let [dvgetter, bytesize] = dvgetters[iectype];
+</xsl:text>
+    <xsl:text>                let value = dvgetter.call(dv,i,true);
+</xsl:text>
+    <xsl:text>                dispatch_value(index, value);
+</xsl:text>
+    <xsl:text>                i += bytesize;
+</xsl:text>
+    <xsl:text>            } else {
+</xsl:text>
+    <xsl:text>                throw new Error("Unknown index "+index)
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
-    <xsl:text>            let d = widget.dispatch;
-</xsl:text>
-    <xsl:text>            if(typeof(d) == "function" &amp;&amp; idxidx == 0){
-</xsl:text>
-    <xsl:text>                return d.call(widget,value);
-</xsl:text>
-    <xsl:text>            }else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
-</xsl:text>
-    <xsl:text>                d[idxidx].call(widget,value);
-</xsl:text>
-    <xsl:text>            }/* else dispatch_0, ..., dispatch_n ? */
-</xsl:text>
-    <xsl:text>            /*else {
-</xsl:text>
-    <xsl:text>                throw new Error("Dunno how to dispatch to widget at index = " + index);
-</xsl:text>
-    <xsl:text>            }*/
+    <xsl:text>        };
+</xsl:text>
+    <xsl:text>    } catch(err) {
+</xsl:text>
+    <xsl:text>        // 1003 is for "Unsupported Data"
+</xsl:text>
+    <xsl:text>        // ws.close(1003, err.message);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // TODO : remove debug alert ?
+</xsl:text>
+    <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // force reload ignoring cache
+</xsl:text>
+    <xsl:text>        location.reload(true);
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function send_blob(data) {
+</xsl:text>
+    <xsl:text>    if(data.length &gt; 0) {
+</xsl:text>
+    <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
+</xsl:text>
+    <xsl:text>    };
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>const typedarray_types = {
+</xsl:text>
+    <xsl:text>    INT: Int16Array,
+</xsl:text>
+    <xsl:text>    BOOL: Uint8Array
+</xsl:text>
+    <xsl:text>    /* TODO */
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function send_reset() {
+</xsl:text>
+    <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>// subscription state, as it should be in hmi server
+</xsl:text>
+    <xsl:text>// hmitree indexed array of integers
+</xsl:text>
+    <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>// subscription state as needed by widget now
+</xsl:text>
+    <xsl:text>// hmitree indexed array of Sets of widgets objects
+</xsl:text>
+    <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function update_subscriptions() {
+</xsl:text>
+    <xsl:text>    let delta = [];
+</xsl:text>
+    <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
+</xsl:text>
+    <xsl:text>        let widgets = subscribers[index];
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // periods are in ms
+</xsl:text>
+    <xsl:text>        let previous_period = subscriptions[index];
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        let new_period = 0;
+</xsl:text>
+    <xsl:text>        if(widgets.size &gt; 0) {
+</xsl:text>
+    <xsl:text>            let maxfreq = 0;
+</xsl:text>
+    <xsl:text>            for(let widget of widgets)
+</xsl:text>
+    <xsl:text>                if(maxfreq &lt; widget.frequency)
+</xsl:text>
+    <xsl:text>                    maxfreq = widget.frequency;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            if(maxfreq != 0)
+</xsl:text>
+    <xsl:text>                new_period = 1000/maxfreq;
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>         
+</xsl:text>
+    <xsl:text>        if(previous_period != new_period) {
+</xsl:text>
+    <xsl:text>            subscriptions[index] = new_period;
+</xsl:text>
+    <xsl:text>            delta.push(
+</xsl:text>
+    <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
+</xsl:text>
+    <xsl:text>                new Uint32Array([index]), 
+</xsl:text>
+    <xsl:text>                new Uint16Array([new_period]));
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function init_widgets() {
-</xsl:text>
-    <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
-</xsl:text>
-    <xsl:text>        let widget = hmi_widgets[id];
-</xsl:text>
-    <xsl:text>        let init = widget.init;
-</xsl:text>
-    <xsl:text>        if(typeof(init) == "function"){
-</xsl:text>
-    <xsl:text>            return init.call(widget);
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>    });
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>// Open WebSocket to relative "/ws" address
-</xsl:text>
-    <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
-</xsl:text>
-    <xsl:text>ws.binaryType = 'arraybuffer';
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>const dvgetters = {
-</xsl:text>
-    <xsl:text>    INT: [DataView.prototype.getInt16, 2],
-</xsl:text>
-    <xsl:text>    BOOL: [DataView.prototype.getInt8, 1]
-</xsl:text>
-    <xsl:text>    /* TODO */
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>// Register message reception handler 
-</xsl:text>
-    <xsl:text>ws.onmessage = function (evt) {
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    let data = evt.data;
-</xsl:text>
-    <xsl:text>    let dv = new DataView(data);
-</xsl:text>
-    <xsl:text>    let i = 0;
-</xsl:text>
-    <xsl:text>    //console.log("Recv something.");
-</xsl:text>
-    <xsl:text>    try {
-</xsl:text>
-    <xsl:text>        for(let hash_int of hmi_hash) {
-</xsl:text>
-    <xsl:text>            if(hash_int != dv.getUint8(i)){
-</xsl:text>
-    <xsl:text>                throw new Error("Hash doesn't match");
-</xsl:text>
-    <xsl:text>            };
-</xsl:text>
-    <xsl:text>            i++;
-</xsl:text>
-    <xsl:text>        };
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        //console.log("Recv something GOOD.");
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        while(i &lt; data.byteLength){
-</xsl:text>
-    <xsl:text>            let index = dv.getUint32(i, true);
-</xsl:text>
-    <xsl:text>            //console.log("Recv something index is "+index);
-</xsl:text>
-    <xsl:text>            i += 4;
-</xsl:text>
-    <xsl:text>            let iectype = hmitree_types[index];
-</xsl:text>
-    <xsl:text>            if(iectype != undefined){
-</xsl:text>
-    <xsl:text>                let [dvgetter, bytesize] = dvgetters[iectype];
-</xsl:text>
-    <xsl:text>                let value = dvgetter.call(dv,i,true);
-</xsl:text>
-    <xsl:text>                dispatch_value(index, value);
-</xsl:text>
-    <xsl:text>                i += bytesize;
-</xsl:text>
-    <xsl:text>            } else {
-</xsl:text>
-    <xsl:text>                throw new Error("Unknown index "+index)
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>        };
-</xsl:text>
-    <xsl:text>    } catch(err) {
-</xsl:text>
-    <xsl:text>        // 1003 is for "Unsupported Data"
-</xsl:text>
-    <xsl:text>        // ws.close(1003, err.message);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        // TODO : remove debug alert ?
-</xsl:text>
-    <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        // force reload ignoring cache
-</xsl:text>
-    <xsl:text>        location.reload(true);
+    <xsl:text>    send_blob(delta);
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function send_hmi_value(index, value) {
+</xsl:text>
+    <xsl:text>    let iectype = hmitree_types[index];
+</xsl:text>
+    <xsl:text>    let jstype = typedarray_types[iectype];
+</xsl:text>
+    <xsl:text>    send_blob([
+</xsl:text>
+    <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
+</xsl:text>
+    <xsl:text>        new Uint32Array([index]), 
+</xsl:text>
+    <xsl:text>        new jstype([value])]);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    cache[index] = value;
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>function change_hmi_value(index, opstr) {
+</xsl:text>
+    <xsl:text>    let op = opstr[0];
+</xsl:text>
+    <xsl:text>    let given_val = opstr.slice(1);
+</xsl:text>
+    <xsl:text>    let old_val = cache[index]
+</xsl:text>
+    <xsl:text>    let new_val;
+</xsl:text>
+    <xsl:text>    switch(op){
+</xsl:text>
+    <xsl:text>      case "=":
+</xsl:text>
+    <xsl:text>        eval("new_val"+opstr);
+</xsl:text>
+    <xsl:text>        break;
+</xsl:text>
+    <xsl:text>      case "+":
+</xsl:text>
+    <xsl:text>      case "-":
+</xsl:text>
+    <xsl:text>      case "*":
+</xsl:text>
+    <xsl:text>      case "/":
+</xsl:text>
+    <xsl:text>        if(old_val != undefined)
+</xsl:text>
+    <xsl:text>            new_val = eval("old_val"+opstr);
+</xsl:text>
+    <xsl:text>        break;
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function send_blob(data) {
-</xsl:text>
-    <xsl:text>    if(data.length &gt; 0) {
-</xsl:text>
-    <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
-</xsl:text>
-    <xsl:text>    };
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>const typedarray_types = {
-</xsl:text>
-    <xsl:text>    INT: Int16Array,
-</xsl:text>
-    <xsl:text>    BOOL: Uint8Array
-</xsl:text>
-    <xsl:text>    /* TODO */
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function send_reset() {
-</xsl:text>
-    <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>// subscription state, as it should be in hmi server
-</xsl:text>
-    <xsl:text>// hmitree indexed array of integers
-</xsl:text>
-    <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>// subscription state as needed by widget now
-</xsl:text>
-    <xsl:text>// hmitree indexed array of Sets of widgets objects
-</xsl:text>
-    <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function update_subscriptions() {
-</xsl:text>
-    <xsl:text>    let delta = [];
-</xsl:text>
-    <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
-</xsl:text>
-    <xsl:text>        let widgets = subscribers[index];
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        // periods are in ms
-</xsl:text>
-    <xsl:text>        let previous_period = subscriptions[index];
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        let new_period = 0;
-</xsl:text>
-    <xsl:text>        if(widgets.size &gt; 0) {
-</xsl:text>
-    <xsl:text>            let maxfreq = 0;
-</xsl:text>
-    <xsl:text>            for(let widget of widgets)
-</xsl:text>
-    <xsl:text>                if(maxfreq &lt; widget.frequency)
-</xsl:text>
-    <xsl:text>                    maxfreq = widget.frequency;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            if(maxfreq != 0)
-</xsl:text>
-    <xsl:text>                new_period = 1000/maxfreq;
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>         
-</xsl:text>
-    <xsl:text>        if(previous_period != new_period) {
-</xsl:text>
-    <xsl:text>            subscriptions[index] = new_period;
-</xsl:text>
-    <xsl:text>            delta.push(
-</xsl:text>
-    <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
-</xsl:text>
-    <xsl:text>                new Uint32Array([index]), 
-</xsl:text>
-    <xsl:text>                new Uint16Array([new_period]));
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>    send_blob(delta);
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function send_hmi_value(index, value) {
-</xsl:text>
-    <xsl:text>    let iectype = hmitree_types[index];
-</xsl:text>
-    <xsl:text>    let jstype = typedarray_types[iectype];
-</xsl:text>
-    <xsl:text>    send_blob([
-</xsl:text>
-    <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
-</xsl:text>
-    <xsl:text>        new Uint32Array([index]), 
-</xsl:text>
-    <xsl:text>        new jstype([value])]);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function change_hmi_value(index, opstr) {
-</xsl:text>
-    <xsl:text>    let op = opstr[0];
-</xsl:text>
-    <xsl:text>    if(op == "=")
-</xsl:text>
-    <xsl:text>        return send_hmi_value(index, Number(opstr.slice(1)));
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    alert('Change '+opstr+" TODO !!! (index :"+index+")");
+    <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
+</xsl:text>
+    <xsl:text>        return send_hmi_value(index, new_val);
 </xsl:text>
     <xsl:text>}
 </xsl:text>
--- a/svghmi/svghmi.js	Tue Oct 22 22:58:55 2019 +0200
+++ b/svghmi/svghmi.js	Thu Oct 24 10:02:07 2019 +0200
@@ -1,10 +1,10 @@
 // svghmi.js
 
+var cache = hmitree_types.map(_ignored => undefined);
+
 function dispatch_value(index, value) {
     let widgets = subscribers[index];
 
-    // TODO : value cache
-    
     if(widgets.size > 0) {
         for(let widget of widgets){
             let idxidx = widget.indexes.indexOf(index);
@@ -22,6 +22,9 @@
             }*/
         }
     }
+
+    cache[index] = value;
+    
 };
 
 function init_widgets() {
@@ -147,14 +150,28 @@
         new Uint32Array([index]), 
         new jstype([value])]);
 
+    cache[index] = value;
 };
 
 function change_hmi_value(index, opstr) {
     let op = opstr[0];
-    if(op == "=")
-        return send_hmi_value(index, Number(opstr.slice(1)));
-
-    alert('Change '+opstr+" TODO !!! (index :"+index+")");
+    let given_val = opstr.slice(1);
+    let old_val = cache[index]
+    let new_val;
+    switch(op){
+      case "=":
+        eval("new_val"+opstr);
+        break;
+      case "+":
+      case "-":
+      case "*":
+      case "/":
+        if(old_val != undefined)
+            new_val = eval("old_val"+opstr);
+        break;
+    }
+    if(new_val != undefined && old_val != new_val)
+        return send_hmi_value(index, new_val);
 }
 
 var current_page;