Fixed way apps are launched in parralel with single log window... Tested in win32 only.
--- a/Beremiz.py Fri Feb 22 19:04:01 2008 +0100
+++ b/Beremiz.py Sun Feb 24 02:06:42 2008 +0100
@@ -36,8 +36,6 @@
from plugger import PluginsRoot
-from wxPopen import wxPopen3
-
SCROLLBAR_UNIT = 10
WINDOW_COLOUR = wx.Colour(240,240,240)
TITLE_COLOUR = wx.Colour(200,200,220)
@@ -72,30 +70,14 @@
# Patch wx.lib.imageutils so that gray is supported on alpha images
import wx.lib.imageutils
+from wx.lib.imageutils import grayOut as old_grayOut
def grayOut(anImage):
- """
- Convert the given image (in place) to a grayed-out
- version, appropriate for a 'disabled' appearance.
- """
- factor = 0.7 # 0 < f < 1. Higher is grayer.
- if anImage.HasMask():
- maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
- else:
- maskColor = None
-
if anImage.HasAlpha():
AlphaData = anImage.GetAlphaData()
else :
AlphaData = None
- data = map(ord, list(anImage.GetData()))
-
- for i in range(0, len(data), 3):
- pixel = (data[i], data[i+1], data[i+2])
- pixel = wx.lib.imageutils.makeGray(pixel, factor, maskColor)
- for x in range(3):
- data[i+x] = pixel[x]
- anImage.SetData(''.join(map(chr, data)))
+ old_grayOut(anImage)
if AlphaData is not None:
anImage.SetAlphaData(AlphaData)
@@ -136,22 +118,23 @@
class LogPseudoFile:
""" Base class for file like objects to facilitate StdOut for the Shell."""
- def __init__(self, output = None):
+ def __init__(self, output):
self.red_white = wx.TextAttr("RED", "WHITE")
self.red_yellow = wx.TextAttr("RED", "YELLOW")
self.black_white = wx.TextAttr("BLACK", "WHITE")
self.default_style = None
self.output = output
- def writelines(self, l):
- map(self.write, l)
-
def write(self, s, style = None):
- if not style : style=self.black_white
+ if style is None : style=self.black_white
+ self.output.Freeze();
if self.default_style != style:
self.output.SetDefaultStyle(style)
self.default_style = style
- self.output.AppendText(s)
+ self.output.AppendText(s)
+ self.output.ScrollLines(s.count('\n')+1)
+ self.output.ShowPosition(self.output.GetLastPosition())
+ self.output.Thaw()
def write_warning(self, s):
self.write(s,self.red_white)
@@ -165,42 +148,6 @@
def isatty(self):
return false
- def LogCommand(self, Command, sz_limit = 100, no_stdout=False):
- self.errlen = 0
- self.exitcode = None
- self.outdata = ""
- self.errdata = ""
-
- def output(v):
- self.outdata += v
- if not no_stdout:
- self.write(v)
-
- def errors(v):
- self.errdata += v
- self.errlen += 1
- if self.errlen > sz_limit:
- p.kill()
- self.write_warning(v)
-
- def fin(pid,ecode):
- self.exitcode = ecode
- if self.exitcode != 0:
- self.write(Command + "\n")
- self.write_warning("exited with status %d (pid %d)\n"%(ecode,pid))
-
- def spin(p):
- while not p.finished:
- wx.Yield()
- time.sleep(0.01)
-
- input = []
- p = wxPopen3(Command, input, output, errors, fin, self.output)
- if p.pid:
- spin(p)
-
- return (self.exitcode, self.outdata, self.errdata)
-
[ID_BEREMIZ, ID_BEREMIZMAINSPLITTER,
ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE,
] = [wx.NewId() for _init_ctrls in range(4)]
@@ -869,9 +816,15 @@
return AddPluginMenu
def GetButtonCallBackFunction(self, plugin, method):
+ """ Generate the callbackfunc for a given plugin method"""
def OnButtonClick(event):
+ # Disable button to prevent re-entrant call
+ event.GetEventObject().Disable()
+ # Call
getattr(plugin,method)(self.Log)
- #self.RefreshVariableLists()
+ # Re-enable button
+ event.GetEventObject().Enable()
+ # Trigger refresh on Idle
wx.CallAfter(self.RefreshAll)
event.Skip()
return OnButtonClick
--- a/plugger.py Fri Feb 22 19:04:01 2008 +0100
+++ b/plugger.py Sun Feb 24 02:06:42 2008 +0100
@@ -8,13 +8,13 @@
import shutil
from xml.dom import minidom
import wx
-import subprocess, ctypes, time, shutil
#Quick hack to be able to find Beremiz IEC tools. Should be config params.
base_folder = os.path.split(sys.path[0])[0]
sys.path.append(os.path.join(base_folder, "plcopeneditor"))
from xmlclass import GenerateClassesFromXSDstring
+from wxPopen import ProcessLogger
_BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -670,6 +670,9 @@
# copy PluginMethods so that it can be later customized
self.PluginMethods = [dic.copy() for dic in self.PluginMethods]
+
+ self.runningPLC = None
+
def HasProjectOpened(self):
"""
@@ -860,7 +863,13 @@
logger.write("Compiling IEC Program in to C code...\n")
# Now compile IEC code into many C files
# files are listed to stdout, and errors to stderr.
- status, result, err_result = logger.LogCommand("%s \"%s\" -I \"%s\" \"%s\""%(iec2c_path, self._getIECcodepath(), ieclib_path, buildpath), no_stdout=True)
+ status, result, err_result = ProcessLogger(
+ logger,
+ "%s \"%s\" -I \"%s\" \"%s\""%(
+ iec2c_path,
+ self._getIECcodepath(),
+ ieclib_path, buildpath),
+ no_stdout=True).spin()
if status:
# Failed !
logger.write_error("Error : IEC to C compiler returned %d\n"%status)
@@ -893,12 +902,16 @@
logger.flush()
logger.write("Start build in %s\n" % buildpath)
+
+ self.EnableMethod("_Clean", True)
+ self.EnableMethod("_showIECcode", True)
# Generate SoftPLC code
if not self._Generate_SoftPLC(logger):
logger.write_error("SoftPLC code generation failed !\n")
return False
+
#logger.write("SoftPLC code generation successfull\n")
logger.write("Generating plugins code ...\n")
@@ -960,7 +973,13 @@
obns.append(obn)
logger.write(" [CC] "+bn+" -> "+obn+"\n")
objectfilename = os.path.splitext(CFile)[0]+".o"
- status, result, err_result = logger.LogCommand("\"%s\" -c \"%s\" -o \"%s\" %s %s"%(compiler, CFile, objectfilename, _CFLAGS, CFLAGS))
+
+ status, result, err_result = ProcessLogger(
+ logger,
+ "\"%s\" -c \"%s\" -o \"%s\" %s %s"%
+ (compiler, CFile, objectfilename, _CFLAGS, CFLAGS)
+ ).spin()
+
if status != 0:
logger.write_error("Build failed\n")
return False
@@ -972,7 +991,14 @@
exe += ".exe"
exe_path = os.path.join(buildpath, exe)
logger.write(" [CC] " + ' '.join(obns)+" -> " + exe + "\n")
- status, result, err_result = logger.LogCommand("\"%s\" \"%s\" -o \"%s\" %s"%(linker, '" "'.join(objs), exe_path, ' '.join(LDFLAGS+[_LDFLAGS])))
+ status, result, err_result = ProcessLogger(
+ logger,
+ "\"%s\" \"%s\" -o \"%s\" %s"%
+ (linker,
+ '" "'.join(objs),
+ exe_path,
+ ' '.join(LDFLAGS+[_LDFLAGS]))
+ ).spin()
if status != 0:
logger.write_error("Build failed\n")
self.EnableMethod("_Run", False)
@@ -1034,24 +1060,42 @@
shutil.rmtree(os.path.join(self._getBuildPath()))
else:
logger.write_error("Build directory already clean\n")
+ self.EnableMethod("_showIECcode", False)
+ self.EnableMethod("_Clean", False)
+ self.EnableMethod("_Run", False)
def _Run(self, logger):
- logger.write("\n")
- self.pid_plc = 0
command_start_plc = os.path.join(self._getBuildPath(),self.GetProjectName() + exe_ext)
if os.path.isfile(command_start_plc):
- logger.write("\nStarting PLC\n")
- self.pid_plc = subprocess.Popen(command_start_plc).pid
+ logger.write("Starting PLC\n")
+ def this_plc_finish_callback(*args):
+ if self.runningPLC is not None:
+ self.runningPLC = None
+ self._Stop(logger)
+ self.runningPLC = ProcessLogger(
+ logger,
+ command_start_plc,
+ finish_callback = this_plc_finish_callback)
+ self.EnableMethod("_Clean", False)
+ self.EnableMethod("_Run", False)
+ self.EnableMethod("_Stop", True)
+ self.EnableMethod("_build", False)
else:
logger.write_error("%s doesn't exist\n" %command_start_plc)
+ def reset_finished(self):
+ self.EnableMethod("_Clean", True)
+ self.EnableMethod("_Run", True)
+ self.EnableMethod("_Stop", False)
+ self.EnableMethod("_build", True)
+
def _Stop(self, logger):
- PROCESS_TERMINATE = 1
- if self.pid_plc != 0:
+ if self.runningPLC is not None:
logger.write("Stopping PLC\n")
- handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid_plc)
- ctypes.windll.kernel32.TerminateProcess(handle, -1)
- ctypes.windll.kernel32.CloseHandle(handle)
+ was_runningPLC = self.runningPLC
+ self.runningPLC = None
+ was_runningPLC.kill()
+ self.reset_finished()
PluginMethods = [
{"bitmap" : os.path.join("images", "editPLC"),
@@ -1078,10 +1122,10 @@
"method" : "_Stop"},
{"bitmap" : os.path.join("images", "ShowIECcode"),
"name" : "Show IEC code",
+ "enabled" : False,
"tooltip" : "Show IEC code generated by PLCGenerator",
"method" : "_showIECcode"},
{"name" : "Edit raw IEC code",
"tooltip" : "Edit raw IEC code added to code generated by PLCGenerator",
"method" : "_editIECrawcode"}
]
-
--- a/runtime/plc_Win32_main.c Fri Feb 22 19:04:01 2008 +0100
+++ b/runtime/plc_Win32_main.c Sun Feb 24 02:06:42 2008 +0100
@@ -3,10 +3,15 @@
#include <time.h>
#include <windows.h>
+int localcount = 0;
+
void timer_notify()
{
struct _timeb timebuffer;
- printf(".");
+ if(++localcount % 50 == 0){
+ printf("PLC tick : %d\n",localcount);
+ fflush(stdout);
+ }
_ftime( &timebuffer );
__CURRENT_TIME.tv_sec = timebuffer.time;
--- a/wxPopen.py Fri Feb 22 19:04:01 2008 +0100
+++ b/wxPopen.py Sun Feb 24 02:06:42 2008 +0100
@@ -22,168 +22,128 @@
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-# based on wxPopen.py from boa-constructor
-#
import time
-from StringIO import StringIO
+import wx
+import subprocess, ctypes
+import threading
+import os
-import wx
+
+class outputThread(threading.Thread):
+ """
+ Thread is used to print the output of a command to the stdout
+ """
+ def __init__(self, Proc, fd, callback=None, endcallback=None):
+ threading.Thread.__init__(self)
+ self.killed = False
+ self.finished = False
+ self.retval = None
+ self.Proc = Proc
+ self.callback = callback
+ self.endcallback = endcallback
+ self.fd = fd
-class ProcessRunnerMix:
+ def run(self):
+ outeof = False
+ self.retval = self.Proc.poll()
+ while not self.retval and not self.killed and not outeof:
+ outchunk = self.fd.readline()
+ if outchunk == '': outeof = True
+ if self.callback :
+ wx.CallAfter(self.callback,outchunk)
+ self.retval=self.Proc.poll()
+ if self.endcallback:
+ err = self.Proc.wait()
+ self.finished = True
+ wx.CallAfter(self.endcallback, self.Proc.pid, self.retval)
- if wx.VERSION < (2, 6, 0):
- def Bind(self, event, function, id = None):
- if id is not None:
- event(self, id, function)
- else:
- event(self, function)
-
- def __init__(self, input, handler=None):
- if handler is None:
- handler = self
- self.handler = handler
- handler.Bind(wx.EVT_IDLE, self.OnIdle)
- handler.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
+class ProcessLogger:
+ def __init__(self, logger, Command, finish_callback=None, no_stdout=False, no_stderr=False):
+ self.logger = logger
+ self.Command = Command
+ self.finish_callback = finish_callback
+ self.no_stdout = no_stdout
+ self.no_stderr = no_stderr
+ self.errlen = 0
+ self.outlen = 0
+ self.exitcode = None
+ self.outdata = ""
+ self.errdata = ""
+ self.finished = False
- input.reverse() # so we can pop
- self.input = input
-
- self.reset()
+ self.Proc = subprocess.Popen(self.Command,
+ cwd = os.getcwd(),
+ stdin = subprocess.PIPE,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.STDOUT)
+# stderr = subprocess.PIPE)
- def reset(self):
- self.process = None
- self.pid = -1
- self.output = []
- self.errors = []
- self.inputStream = None
- self.errorStream = None
- self.outputStream = None
- self.outputFunc = None
- self.errorsFunc = None
- self.finishedFunc = None
- self.finished = False
- self.responded = False
+ self.outt = outputThread(
+ self.Proc,
+ self.Proc.stdout,
+ self.output,
+ self.finish)
- def execute(self, cmd):
- self.process = wx.Process(self.handler)
- self.process.Redirect()
+ self.outt.start()
- self.pid = wx.Execute(cmd, wx.EXEC_ASYNC, self.process)
+# self.errt = outputThread(
+# self.Proc,
+# self.Proc.stderr,
+# self.errors)
+#
+# self.errt.start()
- self.inputStream = self.process.GetOutputStream()
- self.errorStream = self.process.GetErrorStream()
- self.outputStream = self.process.GetInputStream()
+ def output(self,v):
+ self.outdata += v
+ self.outlen += 1
+ if not self.no_stdout:
+ self.logger.write(v)
- #self.OnIdle()
- wx.WakeUpIdle()
-
- def setCallbacks(self, output, errors, finished):
- self.outputFunc = output
- self.errorsFunc = errors
- self.finishedFunc = finished
+ def errors(self,v):
+ self.errdata += v
+ self.errlen += 1
+ if not self.no_stderr:
+ self.logger.write_warning(v)
- def detach(self):
- if self.process is not None:
- self.process.CloseOutput()
- self.process.Detach()
- self.process = None
+ def finish(self, pid,ecode):
+ self.finished = True
+ self.exitcode = ecode
+ if self.exitcode != 0:
+ self.logger.write(self.Command + "\n")
+ self.logger.write_warning("exited with status %s (pid %s)\n"%(str(ecode),str(pid)))
+ if self.finish_callback is not None:
+ self.finish_callback(self,ecode,pid)
def kill(self):
- if self.process is not None:
- self.process.CloseOutput()
- if wx.Process.Kill(self.pid, wx.SIGTERM) != wx.KILL_OK:
- wx.Process.Kill(self.pid, wx.SIGKILL)
- self.process = None
+ self.outt.killed = True
+# self.errt.killed = True
+ if wx.Platform == '__WXMSW__':
+ PROCESS_TERMINATE = 1
+ handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid)
+ ctypes.windll.kernel32.TerminateProcess(handle, -1)
+ ctypes.windll.kernel32.CloseHandle(handle)
+ else:
+ os.kill(self.Proc.pid)
- def updateStream(self, stream, data):
- if stream and stream.CanRead():
- if not self.responded:
- self.responded = True
- text = stream.read()
- data.append(text)
- return text
- else:
- return None
-
- def updateInpStream(self, stream, input):
- if stream and input:
- line = input.pop()
- stream.write(line)
-
- def updateErrStream(self, stream, data):
- return self.updateStream(stream, data)
-
- def updateOutStream(self, stream, data):
- return self.updateStream(stream, data)
-
- def OnIdle(self, event=None):
- if self.process is not None:
- self.updateInpStream(self.inputStream, self.input)
- e = self.updateErrStream(self.errorStream, self.errors)
- if e is not None and self.errorsFunc is not None:
- wx.CallAfter(self.errorsFunc, e)
- o = self.updateOutStream(self.outputStream, self.output)
- if o is not None and self.outputFunc is not None:
- wx.CallAfter(self.outputFunc, o)
-
- #wx.WakeUpIdle()
- #time.sleep(0.001)
-
- def OnProcessEnded(self, event):
- self.OnIdle()
- pid,exitcode = event.GetPid(), event.GetExitCode()
- if self.process:
- self.process.Destroy()
- self.process = None
-
- self.finished = True
-
- # XXX doesn't work ???
- #self.handler.Disconnect(-1, wx.EVT_IDLE)
-
- if self.finishedFunc:
- wx.CallAfter(self.finishedFunc, pid, exitcode)
-
-class ProcessRunner(wx.EvtHandler, ProcessRunnerMix):
- def __init__(self, input):
- wx.EvtHandler.__init__(self)
- ProcessRunnerMix.__init__(self, input)
-
-def wxPopen3(cmd, input, output, errors, finish, handler=None):
- p = ProcessRunnerMix(input, handler)
- p.setCallbacks(output, errors, finish)
- p.execute(cmd)
- return p
-
-def _test():
- app = wx.PySimpleApp()
- f = wx.Frame(None, -1, 'asd')#, style=0)
- f.Show()
-
- def output(v):
- print 'OUTPUT:', v
- def errors(v):
- print 'ERRORS:', v
- def fin():
- p.Close()
- f.Close()
- print 'FINISHED'
-
-
- def spin(p):
- while not p.finished:
+ def spin(self, timeout=None, out_limit=None, err_limit=None, keyword = None, kill_it = True):
+ count = 0
+ while not self.finished:
+ if err_limit and self.errlen > err_limit:
+ break
+ if out_limit and self.outlen > out_limit:
+ break
+ if timeout:
+ if count > timeout:
+ break
+ count += 1
+ if keyword and self.outdata.find(keyword)!=-1:
+ break
wx.Yield()
time.sleep(0.01)
- def evt(self, event):
- input = []
- p = wxPopen3('''c:\\python23\\python.exe -c "print '*'*5000"''',
- input, output, errors, fin, f)
- print p.pid
+ if not self.outt.finished and kill_it:
+ self.kill()
- app.MainLoop()
+ return [self.exitcode, self.outdata, self.errdata]
-if __name__ == '__main__':
- _test()