# 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 @@ - (function(){ + //(function(){ @@ -319,7 +319,7 @@ function dispatch_value(index, value) { - console.log("dispatch_value("+index+value+")"); + console.log("dispatch_value("+index+", "+value+")"); }; @@ -357,74 +357,90 @@ let i = 0; - for(let hash_int of hmi_hash) { - - if(hash_int != dv.getUint8(i)){ - - console.log("Recv non maching hash. Reload."); - - - - // 1003 is for "Unsupported Data" - - ws.close(1003,"Hash doesn't match"); - - - - // TODO : remove debug alert ? - - alert("HMI will be reloaded."); - - - - // force reload ignoring cache - - location.reload(true); + console.log("Recv something."); + + try { + + for(let hash_int of hmi_hash) { + + if(hash_int != dv.getUint8(i)){ + + throw new Error("Hash doesn't match") + + }; + + i++; }; - i++; + + + console.log("Recv something GOOD."); + + + + 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("Error : "+err.message+"\nHMI will be reloaded."); + + + + // force reload ignoring cache + + location.reload(true); + + } + + }; + + + + + + function send_blob(data) { + + if(data.length > 0) { + + ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); }; - - - 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; - - }; - - }; - - - - - - function send_blob(data) { - - if(data.length > 0) { - - ws.send(new Blob([ - - new Uint8Array(hmi_hash), - - data])); - - }; - }; @@ -481,7 +497,7 @@ - let new_period; + let new_period = 0; if(widgets.size > 0) { @@ -489,33 +505,31 @@ for(let widget of widgets) - if(maxfreq < widgets.frequency) - - maxfreq = widgets.frequency; - - - - new_period = 1000/maxfreq; - - } else { - - new_period = 0; + if(maxfreq < widget.frequency) + + maxfreq = widget.frequency; + + + + 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])])); } @@ -613,13 +627,27 @@ 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/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()