# HG changeset patch # User Edouard Tisserant # Date 1568197470 -7200 # Node ID 361366b891cae7c611472a0e3844bf2895f6d609 # Parent 41fc23fd21c41ac781c963bbb0d2c49a2ef237e6 WIP on svghmi, now builds and runs. HTTP serving + WS transport ready, missing actual data to transmit and thread to collect it. diff -r 41fc23fd21c4 -r 361366b891ca svghmi/svghmi.c --- 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); + } diff -r 41fc23fd21c4 -r 361366b891ca svghmi/svghmi.py --- 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 = """ + XSD = """ @@ -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(""" + + +

No SVG file provided

+ + +""") + + 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 diff -r 41fc23fd21c4 -r 361366b891ca svghmi/svghmi_server.py --- /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 diff -r 41fc23fd21c4 -r 361366b891ca tests/svghmi/beremiz.xml --- 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 @@ - + diff -r 41fc23fd21c4 -r 361366b891ca tests/svghmi/plc.xml --- 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 @@ - +