WIP on svghmi, now builds and runs. HTTP serving + WS transport ready, missing actual data to transmit and thread to collect it. svghmi
authorEdouard Tisserant
Wed, 11 Sep 2019 12:24:30 +0200
branchsvghmi
changeset 2771 361366b891ca
parent 2770 41fc23fd21c4
child 2772 3f1dd8312710
WIP on svghmi, now builds and runs. HTTP serving + WS transport ready, missing actual data to transmit and thread to collect it.
svghmi/svghmi.c
svghmi/svghmi.py
svghmi/svghmi_server.py
tests/svghmi/beremiz.xml
tests/svghmi/plc.xml
--- a/svghmi/svghmi.c	Wed Sep 11 11:20:11 2019 +0200
+++ b/svghmi/svghmi.c	Wed Sep 11 12:24:30 2019 +0200
@@ -15,8 +15,8 @@
 
 %(extern_variables_declarations)s
 
-#define ticktime_ns %(PLC_ticktime)d;
-uint16_t ticktime_ms (ticktime_ns>1000000)?
+#define ticktime_ns %(PLC_ticktime)d
+uint16_t ticktime_ms = (ticktime_ns>1000000)?
                      ticktime_ns/1000000:
                      1;
 
@@ -28,13 +28,13 @@
 
 int global_write_dirty = 0;
 
-typedef const struct {
+typedef struct {
     void *ptr;
     __IEC_types_enum type;
     uint32_t buf_index;
 
     /* publish/write/send */
-    int wlock;
+    long wlock;
     /* zero means not subscribed */
     uint16_t refresh_period_ms;
     uint16_t age_ms;
@@ -42,7 +42,7 @@
     buf_state_t wstate;
 
     /* retrieve/read/recv */
-    int rlock;
+    long rlock;
     buf_state_t rstate;
 
 } hmi_tree_item_t;
@@ -82,7 +82,7 @@
         return;
     }
 
-    if(dsc->wstate == buf_set)
+    if(dsc->wstate == buf_set){
         /* if being subscribed */
         if(dsc->refresh_period_ms){
             if(dsc->age_ms + ticktime_ms < dsc->refresh_period_ms){
@@ -115,10 +115,10 @@
     while(AtomicCompareExchange(&dsc->wlock, 0, 1)) sched_yield();
 
     // check for variable being modified
-    if(dsc->wstat == buf_tosend){
+    if(dsc->wstate == buf_tosend){
         // send 
 
-        // TODO write to some socket
+        // TODO call the python callback 
 
         dsc->wstate = buf_free; 
     }
@@ -143,10 +143,7 @@
     bzero(rbuf,sizeof(rbuf));
     bzero(wbuf,sizeof(wbuf));
     
-    // create - connection endpoint
-    //        - sending thread
-    //        - sending semaphore
-    //        - recv thread
+    // TODO - sending pthread condition variable
 
     return 0;
 }
@@ -165,16 +162,21 @@
     global_write_dirty = 0;
     traverse_hmi_tree(write_iterator);
     if(global_write_dirty) {
-        // TODO : set emaphore to wakeup sending thread
+        // TODO : set condition variable to wakeup sending collector
     }
 
 }
 
-void sending_thread_proc(void* args){
+void* collect_updates_to_send(void* args){
+
+    // TODO : get callback from args
+
 
     // TODO : wait for 
-    //        - semaphore
-    //        - next autonomous send thread wakeup. (impl as wait timeout ?) 
+    //        - condition variable
+
+    // TODO add arg to traverse_hmi_tree to pass callback
 
     traverse_hmi_tree(send_iterator);
+
 }
--- a/svghmi/svghmi.py	Wed Sep 11 11:20:11 2019 +0200
+++ b/svghmi/svghmi.py	Wed Sep 11 12:24:30 2019 +0200
@@ -199,7 +199,7 @@
         # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
         #                                     p for p in self._ProgramList]),
 
-        # TODO generate C code to observe/access HMI tree variables
+        # C code to observe/access HMI tree variables
         svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c")
         svghmi_c_file = open(svghmi_c_filepath, 'r')
         svghmi_c_code = svghmi_c_file.read()
@@ -209,7 +209,7 @@
             "extern_variables_declarations": "\n".join(extern_variables_declarations),
             "buffer_size": buf_index,
             "var_access_code": targets.GetCode("var_access.c"),
-            "PLC_ticktime": self.GetCTRoot().GetTicktime()
+            "PLC_ticktime": self.GetCTR().GetTicktime()
             }
 
         gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c")
@@ -217,10 +217,21 @@
         gen_svghmi_c.write(svghmi_c_code)
         gen_svghmi_c.close()
 
-        return (["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), ""
+        # Python based WebSocket HMITree Server
+        svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r')
+        svghmiservercode = svghmiserverfile.read()
+        svghmiserverfile.close()
+
+        runtimefile_path = os.path.join(buildpath, "runtime_svghmi.py")
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write(svghmiservercode)
+        runtimefile.close()
+
+        return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "",
+                ("runtime_svghmi0.py", open(runtimefile_path, "rb")))
 
 class SVGHMI(object):
-    XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+    XSD = """<?xml version="1.0" encoding="utf-8" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:element name="SVGHMI">
         <xsd:complexType>
@@ -245,6 +256,10 @@
             "tooltip": _("Edit HMI"),
             "method":   "_StartInkscape"
         },
+
+        # TODO : HMITree button
+        #        - can drag'n'drop variabes to Inkscape
+
     ]
 
     def _getSVGpath(self, project_path=None):
@@ -294,7 +309,18 @@
         @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
         """
 
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        view_name = self.BaseParams.getName()
+
         svgfile = self._getSVGpath()
+
+        res = ([], "", False)
+
+        target_fname = "sghmi_"+location_str+".xhtml"
+
+        target_path = os.path.join(self._getBuildPath(), target_fname)
+        target_file = open(target_path, 'w')
+
         if os.path.exists(svgfile):
 
             # TODO : move to __init__
@@ -309,6 +335,7 @@
             # call xslt transform on Inkscape's SVG to generate XHTML
             result = transform.transform(svgdom)
            
+            result.write(target_file, encoding="utf-8")
             # print(str(result))
             # print(transform.xslt.error_log)
 
@@ -318,23 +345,29 @@
 
         else:
             # TODO : use default svg that expose the HMI tree as-is 
-            pass
-
-
-        res = ([], "", False)
-
-        targetpath = os.path.join(self._getBuildPath(), "target.xhtml")
-        targetfile = open(targetpath, 'w')
-
-        # TODO : DOM to string
-        targetfile.write("TODO")
-        targetfile.close()
-        res += (("target.js", open(targetpath, "rb")),)
-
-        # TODO add C code to expose HMI tree variables to shared memory
-        # TODO generate a description of shared memory (xml or CSV) 
-        #      that can be loaded by svghmi QTWeb* app or svghmi server
-
+            target_file.write("""<!DOCTYPE html>
+<html>
+<body>
+<h1> No SVG file provided </h1>
+</body>
+</html>
+""")
+
+        target_file.close()
+
+        runtimefile_path = os.path.join(buildpath, "runtime_svghmi1_%s.py" % location_str)
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write("""
+def _runtime_svghmi1_%(location)s_start():
+    svghmi_root.putChild('%(view_name)s',File('%(xhtml)s'))
+
+        """ % {"location": location_str,
+               "xhtml": target_fname,
+               "view_name": view_name})
+
+        runtimefile.close()
+
+        res += (("runtime_svghmi1_%s.py" % location_str, open(runtimefile_path, "rb")),)
 
         return res
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/svghmi_server.py	Wed Sep 11 12:24:30 2019 +0200
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2019: Edouard TISSERANT
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+
+from twisted.web.server import Site
+from twisted.web.resource import Resource
+from twisted.internet import reactor
+from twisted.web.static import File
+
+from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
+from autobahn.twisted.resource import  WebSocketResource
+
+# TODO session list lock
+svghmi_sessions = []
+
+class HMISession(object):
+    def __init__(self, protocol_instance):
+        global svghmi_sessions
+        svghmi_sessions.append(self)
+
+        # TODO multiclient :
+        # get a unique bit index amont other svghmi_sessions,
+        # so that we can match flags passed by C->python callback
+    
+    def __del__(self):
+        global svghmi_sessions
+        svghmi_sessions.remove(self)
+
+    def onMessage():
+        # TODO :  pass it to the C side recieve_message()
+        #    update HMITree
+        #        - values
+        #        - refresh rates / subsriptions
+
+        # TODO multiclient : pass client index as well
+        pass
+         
+
+class HMIProtocol(WebSocketServerProtocol):
+
+    def __init__(self, *args, **kwargs):
+        self._hmi_session = None
+        WebSocketServerProtocol.__init__(self, *args, **kwargs)
+
+    def onOpen(self):
+        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):
+        self._hmi_session.onMessage(msg)
+        print msg
+        #self.sendMessage(msg, binary)
+     
+svghmi_root = None
+svghmi_listener = None
+
+# Called by PLCObject at start
+def _runtime_svghmi0_start():
+    global svghmi_listener, svghmi_root
+
+    svghmi_root = Resource()
+
+    wsfactory = WebSocketServerFactory()
+    wsfactory.protocol = HMIProtocol
+
+    # svghmi_root.putChild("",File(".svg"))
+    svghmi_root.putChild("ws",WebSocketResource(wsfactory))
+
+    sitefactory = Site(svghmi_root)
+
+    svghmi_listener = reactor.listenTCP(8008, sitefactory)
+
+    # TODO
+    # start a thread that call the C part of SVGHMI
+
+
+# Called by PLCObject at stop
+def _runtime_svghmi0_stop():
+    global svghmi_listener
+    svghmi_listener.stopListening
--- a/tests/svghmi/beremiz.xml	Wed Sep 11 11:20:11 2019 +0200
+++ b/tests/svghmi/beremiz.xml	Wed Sep 11 12:24:30 2019 +0200
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='utf-8'?>
-<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
   <TargetType/>
   <Libraries Enable_SVGHMI_Library="true"/>
 </BeremizRoot>
--- a/tests/svghmi/plc.xml	Wed Sep 11 11:20:11 2019 +0200
+++ b/tests/svghmi/plc.xml	Wed Sep 11 12:24:30 2019 +0200
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2019-08-06T14:23:42"/>
-  <contentHeader name="Unnamed" modificationDateTime="2019-08-14T11:10:01">
+  <contentHeader name="Unnamed" modificationDateTime="2019-09-11T11:54:14">
     <coordinateInfo>
       <fbd>
         <scaling x="5" y="5"/>