WIP on svghmi, now builds and runs. HTTP serving + WS transport ready, missing actual data to transmit and thread to collect it.
--- 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"/>