--- a/Beremiz_service.py Fri Sep 07 20:58:13 2018 +0000
+++ b/Beremiz_service.py Wed Sep 12 22:59:30 2018 +0200
@@ -31,13 +31,12 @@
import getopt
import threading
from threading import Thread, Semaphore, Lock
-import traceback
import __builtin__
-import Pyro
-import Pyro.core as pyro
-
-from runtime import PLCObject, ServicePublisher, MainWorker
+
+import runtime
+from runtime.PyroServer import Server
from runtime.xenomai import TryPreloadXenomai
+from runtime import LogMessageAndException
import util.paths as paths
@@ -252,12 +251,11 @@
TBMENU_CHANGE_WD = wx.NewId()
TBMENU_QUIT = wx.NewId()
- def __init__(self, pyroserver, level):
+ def __init__(self, pyroserver):
wx.TaskBarIcon.__init__(self)
self.pyroserver = pyroserver
# Set the image
self.UpdateIcon(None)
- self.level = level
# bind some events
self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START)
@@ -280,15 +278,14 @@
menu = wx.Menu()
menu.Append(self.TBMENU_START, _("Start PLC"))
menu.Append(self.TBMENU_STOP, _("Stop PLC"))
- if self.level == 1:
- menu.AppendSeparator()
- menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name"))
- menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind"))
- menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number"))
- menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
- menu.AppendSeparator()
- menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
- menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
+ menu.AppendSeparator()
+ menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name"))
+ menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind"))
+ menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number"))
+ menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
+ menu.AppendSeparator()
+ menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
+ menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
menu.AppendSeparator()
menu.Append(self.TBMENU_QUIT, _("Quit"))
return menu
@@ -307,19 +304,10 @@
return icon
def OnTaskBarStartPLC(self, evt):
- if self.pyroserver.plcobj is not None:
- plcstatus = self.pyroserver.plcobj.GetPLCstatus()[0]
- if plcstatus is "Stopped":
- self.pyroserver.plcobj.StartPLC()
- else:
- print(_("PLC is empty or already started."))
+ runtime.GetPLCObjectSingleton().StartPLC()
def OnTaskBarStopPLC(self, evt):
- if self.pyroserver.plcobj is not None:
- if self.pyroserver.plcobj.GetPLCstatus()[0] == "Started":
- Thread(target=self.pyroserver.plcobj.StopPLC).start()
- else:
- print(_("PLC is not started."))
+ runtime.GetPLCObjectSingleton().StopPLC()
def OnTaskBarChangeInterface(self, evt):
ip_addr = self.pyroserver.ip_addr
@@ -355,10 +343,7 @@
self.pyroserver.Restart()
def _LiveShellLocals(self):
- if self.pyroserver.plcobj is not None:
- return {"locals": self.pyroserver.plcobj.python_runtime_vars}
- else:
- return {}
+ return {"locals": runtime.GetPLCObjectSingleton().python_runtime_vars}
def OnTaskBarLiveShell(self, evt):
from wx import py
@@ -401,90 +386,6 @@
res = (None, sys.exc_info())
return res
-
-class Server(object):
- def __init__(self, servicename, ip_addr, port,
- workdir, argv,
- statuschange=None, evaluator=default_evaluator,
- pyruntimevars=None):
- self.continueloop = True
- self.daemon = None
- self.servicename = servicename
- self.ip_addr = ip_addr
- self.port = port
- self.workdir = workdir
- self.argv = argv
- self.servicepublisher = None
- self.statuschange = statuschange
- self.evaluator = evaluator
- self.pyruntimevars = pyruntimevars
- self.plcobj = PLCObject(self)
-
- def _to_be_published(self):
- return self.servicename is not None and \
- self.ip_addr is not None and \
- self.ip_addr != "localhost" and \
- self.ip_addr != "127.0.0.1"
-
- def PrintServerInfo(self):
- print(_("Pyro port :"), self.port)
-
- # Beremiz IDE detects LOCAL:// runtime is ready by looking
- # for self.workdir in the daemon's stdout.
- print(_("Current working directory :"), self.workdir)
-
- if self._to_be_published():
- print(_("Publishing service on local network"))
-
- sys.stdout.flush()
-
- def PyroLoop(self, when_ready):
- while self.continueloop:
- Pyro.config.PYRO_MULTITHREADED = 0
- pyro.initServer()
- self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
-
- # pyro never frees memory after connection close if no timeout set
- # taking too small timeout value may cause
- # unwanted diconnection when IDE is kept busy for long periods
- self.daemon.setTimeout(60)
-
- self.daemon.connect(self.plcobj, "PLCObject")
-
- if self._to_be_published():
- self.servicepublisher = ServicePublisher.ServicePublisher()
- self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
-
- when_ready()
- self.daemon.requestLoop()
- self.daemon.sock.close()
-
- def Restart(self):
- self._stop()
-
- def Quit(self):
- self.continueloop = False
- if self.plcobj is not None:
- self.plcobj.StopPLC()
- self.plcobj.UnLoadPLC()
- self._stop()
-
- def _stop(self):
- if self.plcobj is not None:
- self.plcobj.StopPLC()
- if self.servicepublisher is not None:
- self.servicepublisher.UnRegisterService()
- self.servicepublisher = None
- self.daemon.shutdown(True)
-
- def AutoLoad(self):
- self.plcobj.AutoLoad()
- if self.plcobj.GetPLCstatus()[0] == "Stopped":
- if autostart:
- self.plcobj.StartPLC()
- self.plcobj.StatusChange()
-
-
if enabletwisted:
import warnings
with warnings.catch_warnings():
@@ -532,31 +433,11 @@
wx.CallAfter(wx_evaluator, o)
wx_eval_lock.acquire()
return o.res
-
- pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv,
- statuschange, evaluator, pyruntimevars)
-
- taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
else:
- pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv,
- statuschange, pyruntimevars=pyruntimevars)
-
+ evaluator = default_evaluator
# Exception hooks
-
-def LogMessageAndException(msg, exp=None):
- if exp is None:
- exp = sys.exc_info()
- if pyroserver.plcobj is not None:
- pyroserver.plcobj.LogMessage(0, msg + '\n'.join(traceback.format_exception(*exp)))
- else:
- print(msg)
- traceback.print_exception(*exp)
-
-
def LogException(*exp):
LogMessageAndException("", exp)
@@ -606,6 +487,15 @@
sys.path.append(extension_folder)
execfile(os.path.join(extension_folder, extention_file), locals())
+
+runtime.CreatePLCObjectSingleton(
+ WorkingDir, argv, statuschange, evaluator, pyruntimevars)
+
+pyroserver = Server(servicename, given_ip, port)
+
+if havewx:
+ taskbar_instance = BeremizTaskBarIcon(pyroserver)
+
if havetwisted:
if webport is not None:
try:
@@ -635,6 +525,11 @@
pyroserver.PrintServerInfo()
+# Beremiz IDE detects LOCAL:// runtime is ready by looking
+# for self.workdir in the daemon's stdout.
+sys.stdout.write(_("Current working directory :") + WorkingDir + "\n")
+sys.stdout.flush()
+
if havetwisted or havewx:
ui_thread_started = Lock()
ui_thread_started.acquire()
@@ -661,9 +556,15 @@
print("UI thread started successfully.")
try:
- MainWorker.runloop(pyroserver.AutoLoad)
+ runtime.MainWorker.runloop(
+ runtime.GetPLCObjectSingleton().AutoLoad, autostart)
except KeyboardInterrupt:
pass
pyroserver.Quit()
+
+plcobj = runtime.GetPLCObjectSingleton()
+plcobj.StopPLC()
+plcobj.UnLoadPLC()
+
sys.exit(0)
--- a/ProjectController.py Fri Sep 07 20:58:13 2018 +0000
+++ b/ProjectController.py Wed Sep 12 22:59:30 2018 +0200
@@ -267,7 +267,7 @@
self._setBuildPath(None)
self.debug_break = False
self.previous_plcstate = None
- # copy ConfNodeMethods so that it can be later customized
+ # copy StatusMethods so that it can be later customized
self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
def __del__(self):
--- a/bacnet/runtime/device.c Fri Sep 07 20:58:13 2018 +0000
+++ b/bacnet/runtime/device.c Wed Sep 12 22:59:30 2018 +0200
@@ -398,7 +398,7 @@
PROP_OBJECT_TYPE, /* R R ( 79) */
PROP_SYSTEM_STATUS, /* R R (112) */
PROP_VENDOR_NAME, /* R R (121) */
- PROP_VENDOR_IDENTIFIER, /* W R (120) */
+ PROP_VENDOR_IDENTIFIER, /* R R (120) */
PROP_MODEL_NAME, /* W R ( 70) */
PROP_FIRMWARE_REVISION, /* R R ( 44) */
PROP_APPLICATION_SOFTWARE_VERSION, /* R R ( 12) */
@@ -1366,16 +1366,16 @@
apdu_timeout_set((uint16_t) value.type.Unsigned_Int);
}
break;
- case PROP_VENDOR_IDENTIFIER:
- status =
- WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
- &wp_data->error_class, &wp_data->error_code);
- if (status) {
- /* FIXME: bounds check? */
- Device_Set_Vendor_Identifier((uint16_t) value.
- type.Unsigned_Int);
- }
- break;
+// case PROP_VENDOR_IDENTIFIER:
+// status =
+// WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+// &wp_data->error_class, &wp_data->error_code);
+// if (status) {
+// /* FIXME: bounds check? */
+// Device_Set_Vendor_Identifier((uint16_t) value.
+// type.Unsigned_Int);
+// }
+// break;
// case PROP_SYSTEM_STATUS:
// status =
// WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
@@ -1453,6 +1453,7 @@
case PROP_OBJECT_TYPE:
case PROP_SYSTEM_STATUS:
case PROP_VENDOR_NAME:
+ case PROP_VENDOR_IDENTIFIER:
case PROP_FIRMWARE_REVISION:
case PROP_APPLICATION_SOFTWARE_VERSION:
case PROP_LOCAL_TIME:
--- a/bacnet/runtime/server.c Fri Sep 07 20:58:13 2018 +0000
+++ b/bacnet/runtime/server.c Wed Sep 12 22:59:30 2018 +0200
@@ -517,7 +517,7 @@
if (elapsed_seconds) {
last_seconds = current_seconds;
dcc_timer_seconds(elapsed_seconds);
- bvlc_maintenance_timer(elapsed_seconds);
+ //bvlc_maintenance_timer(elapsed_seconds); // already called by dlenv_maintenance_timer() => do _not_ call here!
dlenv_maintenance_timer(elapsed_seconds);
elapsed_milliseconds = elapsed_seconds * 1000;
tsm_timer_milliseconds(elapsed_milliseconds);
--- a/py_ext/plc_python.c Fri Sep 07 20:58:13 2018 +0000
+++ b/py_ext/plc_python.c Wed Sep 12 22:59:30 2018 +0200
@@ -89,6 +89,10 @@
*/
void __PythonEvalFB(int poll, PYTHON_EVAL* data__)
{
+ if(!__GET_VAR(data__->TRIG)){
+ /* ACK is False when TRIG is false, except a pulse when receiving result */
+ __SET_VAR(data__->, ACK,, 0);
+ }
/* detect rising edge on TRIG to trigger evaluation */
if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) ||
/* polling is equivalent to trig on value rather than on rising edge*/
@@ -109,7 +113,7 @@
if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){
/* Copy buffer content into result*/
__SET_VAR(data__->, RESULT,, __GET_VAR(data__->BUFFER));
- /* signal result presece to PLC*/
+ /* signal result presence to PLC*/
__SET_VAR(data__->, ACK,, 1);
/* Mark as free */
__SET_VAR(data__->, STATE,, PYTHON_FB_FREE);
--- a/runtime/NevowServer.py Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/NevowServer.py Wed Sep 12 22:59:30 2018 +0200
@@ -30,6 +30,7 @@
from zope.interface import implements
from nevow import appserver, inevow, tags, loaders, athena, url, rend
from nevow.page import renderer
+from nevow.static import File
from formless import annotate
from formless import webform
from formless import configurable
@@ -77,10 +78,12 @@
class MainPage(athena.LiveElement):
jsClass = u"WebInterface.PLC"
docFactory = loaders.stan(
- tags.div(render=tags.directive('liveElement'))[
- tags.div(id='content')[
- tags.div(render=tags.directive('PLCElement'))]
- ])
+ tags.invisible[
+ tags.div(render=tags.directive('liveElement'))[
+ tags.div(id='content')[
+ tags.div(render=tags.directive('PLCElement'))]
+ ],
+ tags.a(href='settings')['Settings']])
def __init__(self, *a, **kw):
athena.LiveElement.__init__(self, *a, **kw)
@@ -135,11 +138,34 @@
def __init__(self):
configurable.Configurable.__init__(self, None)
self.bindingsNames = []
+ self.infostringcount = 0
def getBindingNames(self, ctx):
return self.bindingsNames
- def addExtension(self, name, desc, fields, btnlabel, callback):
+ def addInfoString(self, label, value, name=None):
+ if isinstance(value, str):
+ def default(*k):
+ return value
+ else:
+ def default(*k):
+ return value()
+
+ if name is None:
+ name = "_infostring_" + str(self.infostringcount)
+ self.infostringcount = self.infostringcount + 1
+
+ def _bind(ctx):
+ return annotate.Property(
+ name,
+ annotate.String(
+ label=label,
+ default=default,
+ immutable=True))
+ setattr(self, 'bind_' + name, _bind)
+ self.bindingsNames.append(name)
+
+ def addSettings(self, name, desc, fields, btnlabel, callback):
def _bind(ctx):
return annotate.MethodBinding(
'action_' + name,
@@ -192,25 +218,27 @@
# This makes webform_css url answer some default CSS
child_webform_css = webform.defaultCSS
+ child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css')
implements(ISettings)
- docFactory = loaders.stan([
- tags.html[
- tags.head[
- tags.title[_("Beremiz Runtime Settings")],
- tags.link(rel='stylesheet',
- type='text/css',
- href=url.here.child("webform_css"))
- ],
- tags.body[
- tags.h1["Runtime settings:"],
- webform.renderForms('staticSettings'),
- tags.h2["Extensions settings:"],
- webform.renderForms('dynamicSettings'),
- ]
- ]
- ])
+ docFactory = loaders.stan([tags.html[
+ tags.head[
+ tags.title[_("Beremiz Runtime Settings")],
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webform_css")),
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webinterface_css"))
+ ],
+ tags.body[
+ tags.a(href='/')['Back'],
+ tags.h1["Runtime settings:"],
+ webform.renderForms('staticSettings'),
+ tags.h1["Extensions settings:"],
+ webform.renderForms('dynamicSettings'),
+ ]]])
def configurable_staticSettings(self, ctx):
return configurable.TypedInterfaceConfigurable(self)
--- a/runtime/PLCObject.py Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/PLCObject.py Wed Sep 12 22:59:30 2018 +0200
@@ -23,7 +23,6 @@
from __future__ import absolute_import
-import thread
from threading import Thread, Lock, Semaphore, Event, Condition
import ctypes
import os
@@ -31,10 +30,10 @@
import traceback
from time import time
import _ctypes # pylint: disable=wrong-import-order
-import Pyro.core as pyro
from runtime.typemapping import TypeTranslator
from runtime.loglevels import LogLevelsDefault, LogLevelsCount
+from runtime import MainWorker
if os.name in ("nt", "ce"):
dlopen = _ctypes.LoadLibrary
@@ -61,107 +60,6 @@
sys.stdout.flush()
-class job(object):
- """
- job to be executed by a worker
- """
- def __init__(self, call, *args, **kwargs):
- self.job = (call, args, kwargs)
- self.result = None
- self.success = False
- self.exc_info = None
-
- def do(self):
- """
- do the job by executing the call, and deal with exceptions
- """
- try:
- call, args, kwargs = self.job
- self.result = call(*args, **kwargs)
- self.success = True
- except Exception:
- self.success = False
- self.exc_info = sys.exc_info()
-
-
-class worker(object):
- """
- serialize main thread load/unload of PLC shared objects
- """
- def __init__(self):
- # Only one job at a time
- self._finish = False
- self._threadID = None
- self.mutex = Lock()
- self.todo = Condition(self.mutex)
- self.done = Condition(self.mutex)
- self.free = Condition(self.mutex)
- self.job = None
-
- def runloop(self, *args, **kwargs):
- """
- meant to be called by worker thread (blocking)
- """
- self._threadID = thread.get_ident()
- if args or kwargs:
- job(*args, **kwargs).do()
- # result is ignored
- self.mutex.acquire()
- while not self._finish:
- self.todo.wait()
- if self.job is not None:
- self.job.do()
- self.done.notify()
- else:
- self.free.notify()
- self.mutex.release()
-
- def call(self, *args, **kwargs):
- """
- creates a job, execute it in worker thread, and deliver result.
- if job execution raise exception, re-raise same exception
- meant to be called by non-worker threads, but this is accepted.
- blocking until job done
- """
-
- _job = job(*args, **kwargs)
-
- if self._threadID == thread.get_ident() or self._threadID is None:
- # if caller is worker thread execute immediately
- _job.do()
- else:
- # otherwise notify and wait for completion
- self.mutex.acquire()
-
- while self.job is not None:
- self.free.wait()
-
- self.job = _job
- self.todo.notify()
- self.done.wait()
- _job = self.job
- self.job = None
- self.mutex.release()
-
- if _job.success:
- return _job.result
- else:
- raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2]
-
- def quit(self):
- """
- unblocks main thread, and terminate execution of runloop()
- """
- # mark queue
- self._finish = True
- self.mutex.acquire()
- self.job = None
- self.todo.notify()
- self.mutex.release()
-
-
-MainWorker = worker()
-
def RunInMain(func):
def func_wrapper(*args, **kwargs):
@@ -169,22 +67,19 @@
return func_wrapper
-class PLCObject(pyro.ObjBase):
- def __init__(self, server):
- pyro.ObjBase.__init__(self)
- self.evaluator = server.evaluator
- self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec...
- self.workingdir = server.workdir
+class PLCObject(object):
+ def __init__(self, WorkingDir, argv, statuschange, evaluator, pyruntimevars):
+ self.workingdir = WorkingDir
+ # FIXME : is argv of any use nowadays ?
+ self.argv = [WorkingDir] + argv # force argv[0] to be "path" to exec...
+ self.statuschange = statuschange
+ self.evaluator = evaluator
+ self.pyruntimevars = pyruntimevars
self.PLCStatus = "Empty"
self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
- self.DummyIteratorLock = None
# Creates fake C funcs proxies
self._InitPLCStubCalls()
- self.daemon = server.daemon
- self.statuschange = server.statuschange
- self.hmi_frame = None
- self.pyruntimevars = server.pyruntimevars
self._loading_error = None
self.python_runtime_vars = None
self.TraceThread = None
@@ -192,7 +87,7 @@
self.Traces = []
# First task of worker -> no @RunInMain
- def AutoLoad(self):
+ def AutoLoad(self, autostart):
# Get the last transfered PLC
try:
self.CurrentPLCFilename = open(
@@ -200,10 +95,15 @@
"r").read().strip() + lib_ext
if self.LoadPLC():
self.PLCStatus = "Stopped"
+ if autostart:
+ self.StartPLC()
+ return
except Exception:
self.PLCStatus = "Empty"
self.CurrentPLCFilename = None
+ self.StatusChange()
+
def StatusChange(self):
if self.statuschange is not None:
for callee in self.statuschange:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/PyroServer.py Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+
+# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+# Copyright (C) 2017: Andrey Skvortsov
+# Copyright (C) 2018: Edouard TISSERANT
+
+# See COPYING file for copyrights details.
+
+import sys
+
+import Pyro
+import Pyro.core as pyro
+import runtime
+from runtime.ServicePublisher import ServicePublisher
+
+class Server(object):
+ def __init__(self, servicename, ip_addr, port):
+ self.continueloop = True
+ self.daemon = None
+ self.servicename = servicename
+ self.ip_addr = ip_addr
+ self.port = port
+ self.servicepublisher = None
+
+ def _to_be_published(self):
+ return self.servicename is not None and \
+ self.ip_addr is not None and \
+ self.ip_addr != "localhost" and \
+ self.ip_addr != "127.0.0.1"
+
+ def PrintServerInfo(self):
+ print(_("Pyro port :"), self.port)
+
+ if self._to_be_published():
+ print(_("Publishing service on local network"))
+
+ sys.stdout.flush()
+
+ def PyroLoop(self, when_ready):
+ while self.continueloop:
+ Pyro.config.PYRO_MULTITHREADED = 0
+ pyro.initServer()
+ self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
+
+ # pyro never frees memory after connection close if no timeout set
+ # taking too small timeout value may cause
+ # unwanted diconnection when IDE is kept busy for long periods
+ self.daemon.setTimeout(60)
+
+ pyro_obj=Pyro.core.ObjBase()
+ pyro_obj.delegateTo(runtime.GetPLCObjectSingleton())
+
+ self.daemon.connect(pyro_obj, "PLCObject")
+
+ if self._to_be_published():
+ self.servicepublisher = ServicePublisher()
+ self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
+
+ when_ready()
+ self.daemon.requestLoop()
+ self.daemon.sock.close()
+
+ def Restart(self):
+ self._stop()
+
+ def Quit(self):
+ self.continueloop = False
+ self._stop()
+
+ def _stop(self):
+ if self.servicepublisher is not None:
+ self.servicepublisher.UnRegisterService()
+ self.servicepublisher = None
+ self.daemon.shutdown(True)
+
+
+
--- a/runtime/WampClient.py Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/WampClient.py Wed Sep 12 22:59:30 2018 +0200
@@ -427,7 +427,8 @@
def RegisterWebSettings(NS):
- NS.ConfigurableSettings.addExtension(
+
+ NS.ConfigurableSettings.addSettings(
"wamp",
_("Wamp Settings"),
webFormInterface,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/Worker.py Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+#
+# Copyright (C) 2018: Edouard TISSERANT
+#
+# See COPYING.Runtime file for copyrights details.
+
+from __future__ import absolute_import
+import thread
+from threading import Lock, Condition
+
+class job(object):
+ """
+ job to be executed by a worker
+ """
+ def __init__(self, call, *args, **kwargs):
+ self.job = (call, args, kwargs)
+ self.result = None
+ self.success = False
+ self.exc_info = None
+
+ def do(self):
+ """
+ do the job by executing the call, and deal with exceptions
+ """
+ try:
+ call, args, kwargs = self.job
+ self.result = call(*args, **kwargs)
+ self.success = True
+ except Exception:
+ self.success = False
+ self.exc_info = sys.exc_info()
+
+
+class worker(object):
+ """
+ serialize main thread load/unload of PLC shared objects
+ """
+ def __init__(self):
+ # Only one job at a time
+ self._finish = False
+ self._threadID = None
+ self.mutex = Lock()
+ self.todo = Condition(self.mutex)
+ self.done = Condition(self.mutex)
+ self.free = Condition(self.mutex)
+ self.job = None
+
+ def runloop(self, *args, **kwargs):
+ """
+ meant to be called by worker thread (blocking)
+ """
+ self._threadID = thread.get_ident()
+ if args or kwargs:
+ job(*args, **kwargs).do()
+ # result is ignored
+ self.mutex.acquire()
+ while not self._finish:
+ self.todo.wait()
+ if self.job is not None:
+ self.job.do()
+ self.done.notify()
+ else:
+ self.free.notify()
+ self.mutex.release()
+
+ def call(self, *args, **kwargs):
+ """
+ creates a job, execute it in worker thread, and deliver result.
+ if job execution raise exception, re-raise same exception
+ meant to be called by non-worker threads, but this is accepted.
+ blocking until job done
+ """
+
+ _job = job(*args, **kwargs)
+
+ if self._threadID == thread.get_ident():
+ # if caller is worker thread execute immediately
+ _job.do()
+ else:
+ # otherwise notify and wait for completion
+ self.mutex.acquire()
+
+ while self.job is not None:
+ self.free.wait()
+
+ self.job = _job
+ self.todo.notify()
+ self.done.wait()
+ _job = self.job
+ self.job = None
+ self.mutex.release()
+
+ if _job.success:
+ return _job.result
+ else:
+ raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2]
+
+ def quit(self):
+ """
+ unblocks main thread, and terminate execution of runloop()
+ """
+ # mark queue
+ self._finish = True
+ self.mutex.acquire()
+ self.job = None
+ self.todo.notify()
+ self.mutex.release()
--- a/runtime/__init__.py Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/__init__.py Wed Sep 12 22:59:30 2018 +0200
@@ -1,28 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# This file is part of Beremiz runtime.
-#
-# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-# See COPYING.Runtime file for copyrights details.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
+from __future__ import absolute_import
+import traceback
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
+from runtime.Worker import worker
+MainWorker = worker()
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+from runtime.PLCObject import PLCObject
-from __future__ import absolute_import
-import os
+_PLCObjectSingleton = None
-from runtime.PLCObject import PLCObject, PLCprint, MainWorker
-import runtime.ServicePublisher
+def GetPLCObjectSingleton():
+ global _PLCObjectSingleton
+ assert(_PLCObjectSingleton is not None)
+ return _PLCObjectSingleton
+
+
+def LogMessageAndException(msg, exp=None):
+ global _PLCObjectSingleton
+ if exp is None:
+ exp = sys.exc_info()
+ if _PLCObjectSingleton is not None:
+ _PLCObjectSingleton.LogMessage(0, msg + '\n'.join(traceback.format_exception(*exp)))
+ else:
+ print(msg)
+ traceback.print_exception(*exp)
+
+def CreatePLCObjectSingleton(*args):
+ global _PLCObjectSingleton
+ _PLCObjectSingleton = PLCObject(*args)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/webinterface.css Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,6 @@
+
+.freeform-label {
+ float: left;
+ width: 30%;
+}
+
--- a/runtime_files.list Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime_files.list Wed Sep 12 22:59:30 2018 +0200
@@ -10,6 +10,7 @@
runtime/PLCObject.py
runtime/NevowServer.py
runtime/webinterface.js
+runtime/webinterface.css
runtime/__init__.py
runtime/ServicePublisher.py
runtime/typemapping.py