beremiz

Parents f27ca37b6e7a
Children e2e498333fbc
Fixed way apps are launched in parralel with single log window... Tested in win32 only.
  • +16 -63
    Beremiz.py
  • +58 -14
    plugger.py
  • +6 -1
    runtime/plc_Win32_main.c
  • +103 -143
    wxPopen.py
  • --- 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
    -
    -class ProcessRunnerMix:
    +import subprocess, ctypes
    +import threading
    +import os
    - 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 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
    - input.reverse() # so we can pop
    - self.input = input
    -
    - self.reset()
    + 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)
    - 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
    +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
    - self.responded = False
    - def execute(self, cmd):
    - self.process = wx.Process(self.handler)
    - self.process.Redirect()
    + self.Proc = subprocess.Popen(self.Command,
    + cwd = os.getcwd(),
    + stdin = subprocess.PIPE,
    + stdout = subprocess.PIPE,
    + stderr = subprocess.STDOUT)
    +# stderr = subprocess.PIPE)
    +
    + self.outt = outputThread(
    + self.Proc,
    + self.Proc.stdout,
    + self.output,
    + self.finish)
    - self.pid = wx.Execute(cmd, wx.EXEC_ASYNC, self.process)
    + self.outt.start()
    - self.inputStream = self.process.GetOutputStream()
    - self.errorStream = self.process.GetErrorStream()
    - self.outputStream = self.process.GetInputStream()
    +# self.errt = outputThread(
    +# self.Proc,
    +# self.Proc.stderr,
    +# self.errors)
    +#
    +# self.errt.start()
    +
    + 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
    -
    - 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
    + 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:
    - 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)
    + os.kill(self.Proc.pid)
    - 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()