# HG changeset patch
# User Edouard Tisserant
# Date 1571320089 -7200
# Node ID f5da343b9b63f27b2ea1f3898f01b185f8bfc2e4
# Parent  ddb2c4668a6b2fd6902a86ffd6eeafff7859f238
SVGHMI: Many fixes. Subscriptions to HMItree seems to be working, and dispatch function is called in JS with good data. Bidirectional communication now really working.

diff -r ddb2c4668a6b -r f5da343b9b63 svghmi/gen_index_xhtml.xslt
--- a/svghmi/gen_index_xhtml.xslt	Tue Oct 15 17:14:48 2019 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Thu Oct 17 15:48:09 2019 +0200
@@ -179,7 +179,7 @@
     <func:result select="exsl:node-set($ast)"/>
   </func:function>
   <xsl:template name="scripts">
-    <xsl:text>(function(){
+    <xsl:text>//(function(){
 </xsl:text>
     <xsl:text>
 </xsl:text>
@@ -319,7 +319,7 @@
 </xsl:text>
     <xsl:text>function dispatch_value(index, value) {
 </xsl:text>
-    <xsl:text>    console.log("dispatch_value("+index+value+")");
+    <xsl:text>    console.log("dispatch_value("+index+", "+value+")");
 </xsl:text>
     <xsl:text>};
 </xsl:text>
@@ -357,74 +357,90 @@
 </xsl:text>
     <xsl:text>    let i = 0;
 </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>            console.log("Recv non maching hash. Reload.");
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            // 1003 is for "Unsupported Data"
-</xsl:text>
-    <xsl:text>            ws.close(1003,"Hash doesn't match");
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            // TODO : remove debug alert ?
-</xsl:text>
-    <xsl:text>            alert("HMI will be reloaded.");
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            // force reload ignoring cache
-</xsl:text>
-    <xsl:text>            location.reload(true);
+    <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>        i++;
+    <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>
+    <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>    while(i &lt; data.length){
-</xsl:text>
-    <xsl:text>        let index = dv.getUint32(i);
-</xsl:text>
-    <xsl:text>        i += 4;
-</xsl:text>
-    <xsl:text>        let iectype = hmitree_types[index];
-</xsl:text>
-    <xsl:text>        let [dvgetter, bytesize] = dvgetters[iectypes];
-</xsl:text>
-    <xsl:text>        value = dvgetter.call(dv,i);
-</xsl:text>
-    <xsl:text>        dispatch_value(index, value);
-</xsl:text>
-    <xsl:text>        i += bytesize;
-</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([
-</xsl:text>
-    <xsl:text>            new Uint8Array(hmi_hash), 
-</xsl:text>
-    <xsl:text>            data]));
-</xsl:text>
-    <xsl:text>    };
-</xsl:text>
     <xsl:text>};
 </xsl:text>
     <xsl:text>
@@ -481,7 +497,7 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        let new_period;
+    <xsl:text>        let new_period = 0;
 </xsl:text>
     <xsl:text>        if(widgets.size &gt; 0) {
 </xsl:text>
@@ -489,33 +505,31 @@
 </xsl:text>
     <xsl:text>            for(let widget of widgets)
 </xsl:text>
-    <xsl:text>                if(maxfreq &lt; widgets.frequency)
-</xsl:text>
-    <xsl:text>                    maxfreq = widgets.frequency;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            new_period = 1000/maxfreq;
-</xsl:text>
-    <xsl:text>        } else {
-</xsl:text>
-    <xsl:text>            new_period = 0;
+    <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>
     <xsl:text>        if(previous_period != new_period) {
 </xsl:text>
     <xsl:text>            subscriptions[index] = new_period;
 </xsl:text>
-    <xsl:text>            delta.push([
+    <xsl:text>            delta.push(new Blob([
 </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>                new Uint16Array([new_period])]));
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
@@ -613,13 +627,27 @@
 </xsl:text>
     <xsl:text>    switch_page(default_page);
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>};
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>})();
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>ws.onclose = function (evt) {
+</xsl:text>
+    <xsl:text>    // TODO : add visible notification while waiting for reload
+</xsl:text>
+    <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
+</xsl:text>
+    <xsl:text>    // TODO : re-enable auto reload when not in debug
+</xsl:text>
+    <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
+</xsl:text>
+    <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>//})();
 </xsl:text>
   </xsl:template>
   <xsl:template mode="page_desc" match="*"/>
diff -r ddb2c4668a6b -r f5da343b9b63 svghmi/gen_index_xhtml.ysl2
--- a/svghmi/gen_index_xhtml.ysl2	Tue Oct 15 17:14:48 2019 +0200
+++ b/svghmi/gen_index_xhtml.ysl2	Thu Oct 17 15:48:09 2019 +0200
@@ -164,7 +164,7 @@
 
     function "scripts"
     {
-        | (function(){
+        | //(function(){
         |
         | var hmi_hash = [«$hmitree/@hash»]; 
 
@@ -242,7 +242,7 @@
         | var default_page = "«$default_page»";
 
         include text svghmi.js
-        | })();
+        | //})();
     }
 
     /*
diff -r ddb2c4668a6b -r f5da343b9b63 svghmi/svghmi.c
--- a/svghmi/svghmi.c	Tue Oct 15 17:14:48 2019 +0200
+++ b/svghmi/svghmi.c	Thu Oct 17 15:48:09 2019 +0200
@@ -65,11 +65,13 @@
 {
     unsigned int i;
     for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){
-        int res;
         hmi_tree_item_t *dsc = &hmi_tree_item[i];
-        if(res = (*fp)(i, dsc))
+        int res = (*fp)(i, dsc);
+        if(res != 0){
             return res;
-    }
+        }
+    }
+    return 0;
 }
 
 #define __Unpack_desc_type hmi_tree_item_t
@@ -116,16 +118,16 @@
 
 static int send_iterator(uint32_t index, hmi_tree_item_t *dsc)
 {
-    int res = 0;
     while(AtomicCompareExchange(&dsc->wlock, 0, 1)) sched_yield();
 
     if(dsc->wstate == buf_tosend)
     {
         uint32_t sz = __get_type_enum_size(dsc->type);
-        if(sbufidx + sizeof(uint32_t) + sz <  sizeof(sbuf))
+        if(sbufidx + sizeof(uint32_t) + sz <=  sizeof(sbuf))
         {
             void *src_p = &wbuf[dsc->buf_index];
             void *dst_p = &sbuf[sbufidx];
+            /* TODO : force into little endian */
             memcpy(dst_p, &index, sizeof(uint32_t));
             memcpy(dst_p + sizeof(uint32_t), src_p, sz);
             dsc->wstate = buf_free;
@@ -133,12 +135,14 @@
         }
         else
         {
-            res = EOVERFLOW;
+            printf("BUG!!! %%d + %%ld + %%d >  %%ld \n", sbufidx, sizeof(uint32_t), sz,  sizeof(sbuf));
+            AtomicCompareExchange(&dsc->wlock, 1, 0);
+            return EOVERFLOW; 
         }
     }
 
     AtomicCompareExchange(&dsc->wlock, 1, 0);
-    return res; 
+    return 0; 
 }
 
 static int read_iterator(uint32_t index, hmi_tree_item_t *dsc)
@@ -224,13 +228,18 @@
 
     if(do_collect) {
         int res;
-        memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
         sbufidx = HMI_HASH_SIZE;
         if((res = traverse_hmi_tree(send_iterator)) == 0)
         {
-            *ptr = &sbuf[0];
-            *size = sbufidx;
-        }
+            if(sbufidx > HMI_HASH_SIZE){
+                memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
+                *ptr = &sbuf[0];
+                *size = sbufidx;
+                return 0;
+            }
+            return ENODATA;
+        }
+        // printf("collected BAD result %%d\n", res);
         return res;
     }
     else
@@ -249,7 +258,6 @@
     const uint8_t* cursor = ptr + HMI_HASH_SIZE;
     const uint8_t* end = ptr + size;
 
-    printf("svghmi_recv_dispatch %%d\n",size);
 
     /* match hmitree fingerprint */
     if(size <= HMI_HASH_SIZE || memcmp(ptr, hmi_hash, HMI_HASH_SIZE) != 0)
@@ -289,9 +297,15 @@
                         AtomicCompareExchange(&dsc->rlock, 1, 0);
                         progress = sz + sizeof(uint32_t) /* index */;
                     }
-                    else return -EINVAL;
-                }
-                else return -EINVAL;
+                    else 
+                    {
+                        return -EINVAL;
+                    }
+                }
+                else 
+                {
+                    return -EINVAL;
+                }
             }
             break;
 
@@ -311,13 +325,19 @@
                 {
                     hmi_tree_item_t *dsc = &hmi_tree_item[index];
                     update_refresh_period(dsc, refresh_period_ms);
-                }
-                else return -EINVAL;
+                    printf("OK\n");
+                }
+                else 
+                {
+                    return -EINVAL;
+                }
 
                 progress = sizeof(uint32_t) /* index */ + 
                            sizeof(uint16_t) /* refresh period */;
             }
             break;
+            default:
+                printf("svghmi_recv_dispatch unknown %%d\n",cmd);
 
         }
         cursor += progress;
diff -r ddb2c4668a6b -r f5da343b9b63 svghmi/svghmi.js
--- a/svghmi/svghmi.js	Tue Oct 15 17:14:48 2019 +0200
+++ b/svghmi/svghmi.js	Thu Oct 17 15:48:09 2019 +0200
@@ -1,7 +1,7 @@
 // svghmi.js
 
 function dispatch_value(index, value) {
-    console.log("dispatch_value("+index+value+")");
+    console.log("dispatch_value("+index+", "+value+")");
 };
 
 // Open WebSocket to relative "/ws" address
@@ -20,39 +20,44 @@
     let data = evt.data;
     let dv = new DataView(data);
     let i = 0;
-    for(let hash_int of hmi_hash) {
-        if(hash_int != dv.getUint8(i)){
-            console.log("Recv non maching hash. Reload.");
+    try {
+        for(let hash_int of hmi_hash) {
+            if(hash_int != dv.getUint8(i)){
+                throw new Error("Hash doesn't match")
+            };
+            i++;
+        };
 
-            // 1003 is for "Unsupported Data"
-            ws.close(1003,"Hash doesn't match");
+        while(i < data.byteLength){
+            let index = dv.getUint32(i, true);
+            console.log("Recv something index is "+index);
+            i += 4;
+            let iectype = hmitree_types[index];
+            if(iectype != undefined){
+                let [dvgetter, bytesize] = dvgetters[iectype];
+                let value = dvgetter.call(dv,i,true);
+                dispatch_value(index, value);
+                i += bytesize;
+            } else {
+                throw new Error("Unknown index "+index)
+            }
+        };
+    } catch(err) {
+        // 1003 is for "Unsupported Data"
+        // ws.close(1003, err.message);
 
-            // TODO : remove debug alert ?
-            alert("HMI will be reloaded.");
+        // TODO : remove debug alert ?
+        alert("Error : "+err.message+"\\\\nHMI will be reloaded.");
 
-            // force reload ignoring cache
-            location.reload(true);
-        };
-        i++;
-    };
-
-    while(i < data.length){
-        let index = dv.getUint32(i);
-        i += 4;
-        let iectype = hmitree_types[index];
-        let [dvgetter, bytesize] = dvgetters[iectypes];
-        value = dvgetter.call(dv,i);
-        dispatch_value(index, value);
-        i += bytesize;
-    };
+        // force reload ignoring cache
+        location.reload(true);
+    }
 };
 
 
 function send_blob(data) {
     if(data.length > 0) {
-        ws.send(new Blob([
-            new Uint8Array(hmi_hash), 
-            data]));
+        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
     };
 };
 
@@ -82,24 +87,23 @@
         // periods are in ms
         let previous_period = subscriptions[index];
 
-        let new_period;
+        let new_period = 0;
         if(widgets.size > 0) {
             let maxfreq = 0;
             for(let widget of widgets)
-                if(maxfreq < widgets.frequency)
-                    maxfreq = widgets.frequency;
+                if(maxfreq < widget.frequency)
+                    maxfreq = widget.frequency;
 
-            new_period = 1000/maxfreq;
-        } else {
-            new_period = 0;
+            if(maxfreq != 0)
+                new_period = 1000/maxfreq;
         }
 
         if(previous_period != new_period) {
             subscriptions[index] = new_period;
-            delta.push([
+            delta.push(new Blob([
                 new Uint8Array([2]), /* subscribe = 2 */
                 new Uint32Array([index]), 
-                new Uint16Array([new_period])]);
+                new Uint16Array([new_period])]));
         }
         
     }
@@ -148,6 +152,13 @@
     send_reset();
     // show main page
     switch_page(default_page);
+};
+
+ws.onclose = function (evt) {
+    // TODO : add visible notification while waiting for reload
+    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
+    // TODO : re-enable auto reload when not in debug
+    //window.setTimeout(() => location.reload(true), 10000);
+    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
 
 };
-
diff -r ddb2c4668a6b -r f5da343b9b63 svghmi/svghmi_server.py
--- a/svghmi/svghmi_server.py	Tue Oct 15 17:14:48 2019 +0200
+++ b/svghmi/svghmi_server.py	Thu Oct 17 15:48:09 2019 +0200
@@ -6,6 +6,7 @@
 # See COPYING file for copyrights details.
 
 from __future__ import absolute_import
+import errno
 
 from twisted.web.server import Site
 from twisted.web.resource import Resource
@@ -13,6 +14,7 @@
 from twisted.web.static import File
 
 from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
+from autobahn.websocket.protocol import WebSocketProtocol
 from autobahn.twisted.resource import  WebSocketResource
 
 # TODO multiclient :
@@ -25,7 +27,7 @@
 svghmi_send_collect.restype = ctypes.c_int # error or 0
 svghmi_send_collect.argtypes = [
     ctypes.POINTER(ctypes.c_uint32),  # size
-    ctypes.POINTER(ctypes.c_char_p)]  # data ptr
+    ctypes.POINTER(ctypes.c_void_p)]  # data ptr
 # TODO multiclient : switch to arrays
 
 svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
@@ -38,10 +40,11 @@
 class HMISession(object):
     def __init__(self, protocol_instance):
         global svghmi_session
-
-        # TODO: kill existing session for robustness
-        assert(svghmi_session is None)
-
+        
+        # Single client :
+        # Creating a new HMISession closes pre-existing HMISession
+        if svghmi_session is not None:
+            svghmi_session.close()
         svghmi_session = self
         self.protocol_instance = protocol_instance
 
@@ -50,13 +53,11 @@
         # get a unique bit index amont other svghmi_sessions,
         # so that we can match flags passed by C->python callback
 
-    def __del__(self):
+    def close(self):
         global svghmi_session
-        assert(svghmi_session)
-        svghmi_session = None
-
-        # TODO multiclient :
-        # svghmi_sessions.remove(self)
+        if svghmi_session == self:
+            svghmi_session = None
+        self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
 
     def onMessage(self, msg):
         # pass message to the C side recieve_message()
@@ -66,7 +67,8 @@
 
 
     def sendMessage(self, msg):
-        self.sendMessage(msg, True)
+        self.protocol_instance.sendMessage(msg, True)
+        return 0
 
 class HMIProtocol(WebSocketServerProtocol):
 
@@ -75,18 +77,15 @@
         WebSocketServerProtocol.__init__(self, *args, **kwargs)
 
     def onOpen(self):
+        assert(self._hmi_session is None)
         self._hmi_session = HMISession(self)
-        print "open"
 
     def onClose(self, wasClean, code, reason):
-        del self._hmi_session
         self._hmi_session = None
-        print "close"
 
     def onMessage(self, msg, isBinary):
+        assert(self._hmi_session is not None)
         self._hmi_session.onMessage(msg)
-        # print msg
-        #self.sendMessage(msg, binary)
 
 class HMIWebSocketServerFactory(WebSocketServerFactory):
     protocol = HMIProtocol
@@ -98,14 +97,17 @@
 def SendThreadProc():
    global svghmi_session
    size = ctypes.c_uint32()
-   ptr = ctypes.c_char_p()
+   ptr = ctypes.c_void_p()
    res = 0
-   while svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) == 0 and \
-         svghmi_session is not None and \
-         svghmi_session.sendMessage(ctypes.string_at(ptr,size)) == 0:
-         pass
+   while True:
+       res=svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr))
+       if res == 0:
+           # TODO multiclient : dispatch to sessions
+           if svghmi_session is not None:
+               svghmi_session.sendMessage(ctypes.string_at(ptr.value,size.value))
+       elif res not in [errno.EAGAIN, errno.ENODATA]:
+           break
 
-       # TODO multiclient : dispatch to sessions
 
 
 
@@ -125,7 +127,9 @@
 
 # Called by PLCObject at stop
 def _runtime_svghmi0_stop():
-    global svghmi_listener, svghmi_root, svghmi_send_thread
+    global svghmi_listener, svghmi_root, svghmi_send_thread, svghmi_session
+    if svghmi_session is not None:
+        svghmi_session.close()
     svghmi_root.delEntity("ws")
     svghmi_root = None
     svghmi_listener.stopListening()