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.
--- 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 < 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 > 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 < 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 > 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 > 0) {
</xsl:text>
@@ -489,33 +505,31 @@
</xsl:text>
<xsl:text> for(let widget of widgets)
</xsl:text>
- <xsl:text> if(maxfreq < 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 < 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(() => 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="*"/>
--- 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
- | })();
+ | //})();
}
/*
--- 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;
--- 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+".");
};
-
--- 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()