--- 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)"/>
<xsl:template name="scripts">
+ <xsl:text>//(function(){ @@ -319,7 +319,7 @@
<xsl:text>function dispatch_value(index, value) {
- <xsl:text> console.log("dispatch_value("+index+value+")");
+ <xsl:text> console.log("dispatch_value("+index+", "+value+")"); @@ -357,55 +357,75 @@
- <xsl:text> for(let hash_int of hmi_hash) {
+ <xsl:text> console.log("Recv something."); + <xsl:text> for(let hash_int of hmi_hash) { + <xsl:text> if(hash_int != dv.getUint8(i)){ - <xsl:text> if(hash_int != dv.getUint8(i)){
+ <xsl:text> throw new Error("Hash doesn't match") - <xsl:text> console.log("Recv non maching hash. Reload.");
- <xsl:text> // 1003 is for "Unsupported Data"
- <xsl:text> ws.close(1003,"Hash doesn't match");
- <xsl:text> // TODO : remove debug alert ?
- <xsl:text> alert("HMI will be reloaded.");
+ <xsl:text> console.log("Recv something GOOD."); - <xsl:text> // force reload ignoring cache
+ <xsl:text> while(i < data.byteLength){ + <xsl:text> let index = dv.getUint32(i, true); + <xsl:text> console.log("Recv something index is "+index); + <xsl:text> let iectype = hmitree_types[index]; + <xsl:text> if(iectype != undefined){ - <xsl:text> location.reload(true);
+ <xsl:text> let [dvgetter, bytesize] = dvgetters[iectype]; + <xsl:text> let value = dvgetter.call(dv,i,true); + <xsl:text> dispatch_value(index, value); + <xsl:text> i += bytesize; + <xsl:text> throw new Error("Unknown index "+index)
+ <xsl:text> } catch(err) {
+ <xsl:text> // 1003 is for "Unsupported Data" + <xsl:text> // ws.close(1003, err.message); - <xsl:text> while(i < data.length){
- <xsl:text> let index = dv.getUint32(i);
+ <xsl:text> // TODO : remove debug alert ?
+ <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); - <xsl:text> let iectype = hmitree_types[index];
- <xsl:text> let [dvgetter, bytesize] = dvgetters[iectypes];
- <xsl:text> value = dvgetter.call(dv,i);
+ <xsl:text> // force reload ignoring cache - <xsl:text> dispatch_value(index, value);
+ <xsl:text> location.reload(true); - <xsl:text> i += bytesize;
@@ -417,11 +437,7 @@
<xsl:text> if(data.length > 0) {
- <xsl:text> ws.send(new Blob([
- <xsl:text> new Uint8Array(hmi_hash),
+ <xsl:text> ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); @@ -481,7 +497,7 @@
- <xsl:text> let new_period;
+ <xsl:text> let new_period = 0; <xsl:text> if(widgets.size > 0) {
@@ -489,33 +505,31 @@
<xsl:text> for(let widget of widgets)
- <xsl:text> if(maxfreq < widgets.frequency)
+ <xsl:text> if(maxfreq < widget.frequency) - <xsl:text> maxfreq = widgets.frequency;
+ <xsl:text> maxfreq = widget.frequency; - <xsl:text> new_period = 1000/maxfreq;
+ <xsl:text> if(maxfreq != 0)
- <xsl:text> new_period = 0;
+ <xsl:text> new_period = 1000/maxfreq;
<xsl:text> if(previous_period != new_period) {
<xsl:text> subscriptions[index] = new_period;
- <xsl:text> delta.push([
+ <xsl:text> delta.push(new Blob([ <xsl:text> new Uint8Array([2]), /* subscribe = 2 */
<xsl:text> new Uint32Array([index]),
- <xsl:text> new Uint16Array([new_period])]);
+ <xsl:text> new Uint16Array([new_period])])); @@ -613,13 +627,27 @@
<xsl:text> switch_page(default_page);
+ <xsl:text>ws.onclose = function (evt) { + <xsl:text> // TODO : add visible notification while waiting for reload + <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); + <xsl:text> // TODO : re-enable auto reload when not in debug + <xsl:text> //window.setTimeout(() => location.reload(true), 10000); + <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
<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 @@
| var hmi_hash = [«$hmitree/@hash»];
@@ -242,7 +242,7 @@
| var default_page = "«$default_page»";
--- 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 @@
for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){
hmi_tree_item_t *dsc = &hmi_tree_item[i];
- if(res = (*fp)(i, dsc))
+ int res = (*fp)(i, dsc); #define __Unpack_desc_type hmi_tree_item_t
@@ -116,16 +118,16 @@
static int send_iterator(uint32_t index, hmi_tree_item_t *dsc)
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);
@@ -133,12 +135,14 @@
+ printf("BUG!!! %%d + %%ld + %%d > %%ld \n", sbufidx, sizeof(uint32_t), sz, sizeof(sbuf)); + AtomicCompareExchange(&dsc->wlock, 1, 0); AtomicCompareExchange(&dsc->wlock, 1, 0);
static int read_iterator(uint32_t index, hmi_tree_item_t *dsc)
@@ -224,13 +228,18 @@
- memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
if((res = traverse_hmi_tree(send_iterator)) == 0)
+ if(sbufidx > HMI_HASH_SIZE){ + memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE); + // printf("collected BAD result %%d\n", res); @@ -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 */;
@@ -311,13 +325,19 @@
hmi_tree_item_t *dsc = &hmi_tree_item[index];
update_refresh_period(dsc, refresh_period_ms);
progress = sizeof(uint32_t) /* index */ +
sizeof(uint16_t) /* refresh period */;
+ printf("svghmi_recv_dispatch unknown %%d\n",cmd); --- 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 @@
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 dv = new DataView(data);
- 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
+ for(let hash_int of hmi_hash) { + if(hash_int != dv.getUint8(i)){ + throw new Error("Hash doesn't match")
- while(i < data.length){
- let index = dv.getUint32(i);
- let iectype = hmitree_types[index];
- let [dvgetter, bytesize] = dvgetters[iectypes];
- value = dvgetter.call(dv,i);
- dispatch_value(index, value);
+ while(i < data.byteLength){ + let index = dv.getUint32(i, true); + console.log("Recv something index is "+index); + let iectype = hmitree_types[index]; + if(iectype != undefined){ + let [dvgetter, bytesize] = dvgetters[iectype]; + let value = dvgetter.call(dv,i,true); + dispatch_value(index, value); + throw new Error("Unknown index "+index) + // 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 function send_blob(data) {
- new Uint8Array(hmi_hash),
+ ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); @@ -82,24 +87,23 @@
let previous_period = subscriptions[index];
for(let widget of widgets)
- if(maxfreq < widgets.frequency)
- maxfreq = widgets.frequency;
+ if(maxfreq < widget.frequency) + maxfreq = widget.frequency; - new_period = 1000/maxfreq;
+ new_period = 1000/maxfreq; if(previous_period != new_period) {
subscriptions[index] = new_period;
new Uint8Array([2]), /* subscribe = 2 */
new Uint32Array([index]),
- new Uint16Array([new_period])]);
+ new Uint16Array([new_period])])); @@ -148,6 +152,13 @@
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
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
@@ -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):
- # TODO: kill existing session for robustness
- assert(svghmi_session is None)
+ # Creating a new HMISession closes pre-existing HMISession + if svghmi_session is not None: 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
- # svghmi_sessions.remove(self)
+ if svghmi_session == self: + 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) class HMIProtocol(WebSocketServerProtocol):
@@ -75,18 +77,15 @@
WebSocketServerProtocol.__init__(self, *args, **kwargs)
+ assert(self._hmi_session is None) self._hmi_session = HMISession(self)
def onClose(self, wasClean, code, reason):
def onMessage(self, msg, isBinary):
+ assert(self._hmi_session is not None) self._hmi_session.onMessage(msg)
- #self.sendMessage(msg, binary)
class HMIWebSocketServerFactory(WebSocketServerFactory):
@@ -98,14 +97,17 @@
- ptr = ctypes.c_char_p()
+ ptr = ctypes.c_void_p() - 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:
+ res=svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) + # 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]: - # 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_root.delEntity("ws")
svghmi_listener.stopListening()