Added auto-reconnect for runtime. Fixed Beremiz closing problem caused by remaining twisted reactor thread in IDE.
authorEdouard Tisserant
Sun, 08 Feb 2015 22:39:17 +0100
changeset 1441 826730e60407
parent 1440 e8daabf2c438
child 1442 ad9a7853dea2
Added auto-reconnect for runtime. Fixed Beremiz closing problem caused by remaining twisted reactor thread in IDE.
Beremiz.py
connectors/PYRO/__init__.py
connectors/WAMP/__init__.py
controls/LogViewer.py
runtime/WampClient.py
--- a/Beremiz.py	Sun Feb 08 16:50:54 2015 +0100
+++ b/Beremiz.py	Sun Feb 08 22:39:17 2015 +0100
@@ -36,6 +36,7 @@
 
 CWD = os.path.split(os.path.realpath(__file__))[0]
 
+
 def Bpath(*args):
     return os.path.join(CWD,*args)
 
@@ -634,6 +635,14 @@
         else:
             return IDEFrame.LoadTab(self, notebook, page_infos)
 
+    # Strange hack required by WAMP connector, using twisted.
+    # Twisted reactor needs to be stopped only before quit,
+    # since it cannot be restarted
+    ToDoBeforeQuit = []
+    def AddToDoBeforeQuit(self, Thing):
+        self.ToDoBeforeQuit.append(Thing)
+        print self.ToDoBeforeQuit
+
     def OnCloseFrame(self, event):
         for evt_type in [wx.EVT_SET_FOCUS,
                          wx.EVT_KILL_FOCUS,
@@ -646,6 +655,10 @@
 
             self.SaveLastState()
 
+            for Thing in self.ToDoBeforeQuit :
+                Thing()
+            self.ToDoBeforeQuit = []
+
             event.Skip()
         else:
             event.Veto()
--- a/connectors/PYRO/__init__.py	Sun Feb 08 16:50:54 2015 +0100
+++ b/connectors/PYRO/__init__.py	Sun Feb 08 22:39:17 2015 +0100
@@ -94,7 +94,7 @@
         return None
 
 
-    class PyroProxyProxy:
+    class PyroProxyProxy(object):
         """
         A proxy proxy class to handle Beremiz Pyro interface specific behavior.
         And to put pyro exception catcher in between caller and pyro proxy
--- a/connectors/WAMP/__init__.py	Sun Feb 08 16:50:54 2015 +0100
+++ b/connectors/WAMP/__init__.py	Sun Feb 08 22:39:17 2015 +0100
@@ -19,7 +19,7 @@
 #License along with this library; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-import sys, traceback
+import sys, traceback, atexit
 #from twisted.python import log
 from twisted.internet import reactor, threads
 from autobahn.twisted import wamp
@@ -30,18 +30,18 @@
 from threading import Thread, Event
 
 _WampSession = None
-_ReactorThread = None
+_WampConnection = None
 _WampSessionEvent = Event()
 
 class WampSession(wamp.ApplicationSession):
     def onJoin(self, details):
-        global _WampSession
+        global _WampSession, _WampSessionEvent
         _WampSession = self
         _WampSessionEvent.set()
         print 'WAMP session joined for :', self.config.extra["ID"]
 
     def onLeave(self, details):
-        global _WampSession
+        global _WampSession, _WampSessionEvent
         _WampSessionEvent.clear()
         _WampSession = None
         print 'WAMP session left'
@@ -56,8 +56,6 @@
     WAMP://127.0.0.1:12345/path#realm#ID
     WAMPS://127.0.0.1:12345/path#realm#ID
     """
-    global _WampSession, _ReactorThread, _WampSessionEvent
-
     servicetype, location = uri.split("://")
     urlpath, realm, ID = location.split('#')
     urlprefix = {"WAMP":"ws",
@@ -90,8 +88,16 @@
         confnodesroot.logger.write(_("WAMP connecting to URL : %s\n")%url)
         return conn
 
+    AddToDoBeforeQuit = confnodesroot.AppFrame.AddToDoBeforeQuit
+    def ThreadProc():
+        global _WampConnection
+        _WampConnection = RegisterWampClient()
+        AddToDoBeforeQuit(reactor.stop)
+        reactor.run(installSignalHandlers=False)
+
     def WampSessionProcMapper(funcname):
         def catcher_func(*args,**kwargs):
+            global _WampSession
             if _WampSession is not None :
                 try:
                     return threads.blockingCallFromThread(
@@ -110,21 +116,21 @@
 
     class WampPLCObjectProxy(object):
         def __init__(self):
+            global _WampSessionEvent, _WampConnection
             if not reactor.running:
-                def ThreadProc():
-                    self.connection = RegisterWampClient()
-                    reactor.run(installSignalHandlers=False)
                 Thread(target=ThreadProc).start()
             else:
-                self.connection = threads.blockingCallFromThread(
+                _WampConnection = threads.blockingCallFromThread(
                     reactor, RegisterWampClient)
             if not _WampSessionEvent.wait(5):
-                self.connection.stopConnecting()
+                _WampConnection = stopConnecting()
                 raise Exception, _("WAMP connection timeout")
 
         def __del__(self):
-            self.connection.disconnect()
-            #reactor.Stop()
+            global _WampConnection
+            _WampConnection.disconnect()
+            #
+            # reactor.stop()
 
         def __getattr__(self, attrName):
             member = self.__dict__.get(attrName, None)
--- a/controls/LogViewer.py	Sun Feb 08 16:50:54 2015 +0100
+++ b/controls/LogViewer.py	Sun Feb 08 22:39:17 2015 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard. 
+#based on the plcopen standard.
 #
 #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
 #
@@ -32,6 +32,7 @@
 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
 from targets.typemapping import LogLevelsCount, LogLevels
 from util.BitmapLibrary import GetBitmap
+from weakref import proxy
 
 THUMB_SIZE_RATIO = 1. / 8.
 
@@ -46,7 +47,7 @@
                 wx.Point(xoffset + width - 1, yoffset - height + 1)]
 
 class LogScrollBar(wx.Panel):
-    
+
     def __init__(self, parent, size):
         wx.Panel.__init__(self, parent, size=size)
         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
@@ -55,14 +56,14 @@
         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.Bind(wx.EVT_SIZE, self.OnResize)
-        
+
         self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
         self.ThumbScrollingStartPos = None
-    
+
     def GetRangeRect(self):
         width, height = self.GetClientSize()
         return wx.Rect(0, width, width, height - 2 * width)
-    
+
     def GetThumbRect(self):
         width, height = self.GetClientSize()
         range_rect = self.GetRangeRect()
@@ -72,7 +73,7 @@
         thumb_start = int(thumb_center_position - thumb_size / 2.)
         thumb_end = int(thumb_center_position + thumb_size / 2.)
         return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
-    
+
     def RefreshThumbPosition(self, thumb_position=None):
         if thumb_position is None:
             thumb_position = self.ThumbPosition
@@ -84,7 +85,7 @@
             self.ThumbPosition = thumb_position
             self.Parent.SetScrollSpeed(self.ThumbPosition)
         self.Refresh()
-    
+
     def OnLeftDown(self, event):
         self.CaptureMouse()
         posx, posy = event.GetPosition()
@@ -103,14 +104,14 @@
         elif posy > height - width:
             self.Parent.ScrollMessagePanelByPage(-1)
         event.Skip()
-        
+
     def OnLeftUp(self, event):
         self.ThumbScrollingStartPos = None
         self.RefreshThumbPosition(0.)
         if self.HasCapture():
             self.ReleaseMouse()
         event.Skip()
-        
+
     def OnMotion(self, event):
         if event.Dragging() and self.ThumbScrollingStartPos is not None:
             posx, posy = event.GetPosition()
@@ -121,32 +122,32 @@
             self.RefreshThumbPosition(
                 max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
         event.Skip()
-    
+
     def OnResize(self, event):
         self.Refresh()
         event.Skip()
-    
+
     def OnEraseBackground(self, event):
         pass
-    
+
     def OnPaint(self, event):
         dc = wx.BufferedPaintDC(self)
         dc.Clear()
         dc.BeginDrawing()
-        
+
         gc = wx.GCDC(dc)
-        
+
         width, height = self.GetClientSize()
-        
+
         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
         gc.SetBrush(wx.GREY_BRUSH)
-        
+
         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
-        
+
         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
-        
+
         thumb_rect = self.GetThumbRect()
         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
                                  thumb_rect.width, thumb_rect.height)
@@ -158,71 +159,71 @@
             colour = wx.NamedColour("LIGHT GREY")
             gc.SetPen(wx.Pen(colour))
             gc.SetBrush(wx.Brush(colour))
-        
-            gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y, 
+
+            gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y,
                              exclusion_rect.width, exclusion_rect.height)
-        
+
         gc.SetPen(wx.GREY_PEN)
         gc.SetBrush(wx.GREY_BRUSH)
-        
+
         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
-        
+
         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
-            
-        gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
+
+        gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
                          thumb_rect.width, thumb_rect.height)
-        
+
         dc.EndDrawing()
         event.Skip()
 
 BUTTON_SIZE = (30, 15)
 
 class LogButton():
-    
+
     def __init__(self, label, callback):
         self.Position = wx.Point(0, 0)
         self.Size = wx.Size(*BUTTON_SIZE)
         self.Label = label
         self.Shown = True
         self.Callback = callback
-    
+
     def __del__(self):
         self.callback = None
-    
+
     def GetSize(self):
         return self.Size
-    
+
     def SetPosition(self, x, y):
         self.Position = wx.Point(x, y)
-    
+
     def HitTest(self, x, y):
-        rect = wx.Rect(self.Position.x, self.Position.y, 
+        rect = wx.Rect(self.Position.x, self.Position.y,
                        self.Size.width, self.Size.height)
         if rect.InsideXY(x, y):
             return True
         return False
-    
+
     def ProcessCallback(self):
         if self.Callback is not None:
             wx.CallAfter(self.Callback)
-            
+
     def Draw(self, dc):
         dc.SetPen(wx.TRANSPARENT_PEN)
         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
-        
-        dc.DrawRectangle(self.Position.x, self.Position.y, 
+
+        dc.DrawRectangle(self.Position.x, self.Position.y,
                          self.Size.width, self.Size.height)
-        
+
         w, h = dc.GetTextExtent(self.Label)
-        dc.DrawText(self.Label, 
-            self.Position.x + (self.Size.width - w) / 2, 
+        dc.DrawText(self.Label,
+            self.Position.x + (self.Size.width - w) / 2,
             self.Position.y + (self.Size.height - h) / 2)
 
 DATE_INFO_SIZE = 10
 MESSAGE_INFO_SIZE = 18
 
 class LogMessage:
-    
+
     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
         self.Date = datetime.utcfromtimestamp(tv_sec)
         self.Seconds = self.Date.second + tv_nsec * 1e-9
@@ -232,12 +233,12 @@
         self.LevelBitmap = level_bitmap
         self.Message = msg
         self.DrawDate = True
-    
+
     def __cmp__(self, other):
         if self.Date == other.Date:
             return cmp(self.Seconds, other.Seconds)
         return cmp(self.Date, other.Date)
-    
+
     def GetFullText(self):
         date = self.Date.replace(second=int(self.Seconds))
         nsec = (self.Seconds % 1.) * 1e9
@@ -245,25 +246,25 @@
             LogLevels[self.Level],
             str(date), nsec,
             self.Message)
-    
+
     def Draw(self, dc, offset, width, draw_date):
         if draw_date:
             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
             dw, dh = dc.GetTextExtent(datetime_text)
             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
             offset += DATE_INFO_SIZE
-        
+
         seconds_text = "%12.9f" % self.Seconds
         sw, sh = dc.GetTextExtent(seconds_text)
         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
-        
+
         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
-        
+
         text = self.Message.replace("\n", " ")
         mw, mh = dc.GetTextExtent(text)
         dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
-        
+
     def GetHeight(self, draw_date):
         if draw_date:
             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
@@ -280,18 +281,18 @@
                             (_("1s"), SECOND)]
 
 class LogViewer(DebugViewer, wx.Panel):
-    
+
     def __init__(self, parent, window):
         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
         DebugViewer.__init__(self, None, False, False)
-        
+
         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(1)
-        
+
         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
         main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
-        
+
         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
         self.MessageFilter.Append(_("All"))
         levels = LogLevels[:3]
@@ -300,28 +301,28 @@
             self.MessageFilter.Append(_(level))
         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-        
+
         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
         self.SearchMessage.ShowSearchButton(True)
         self.SearchMessage.ShowCancelButton(True)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
-        self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
+        self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
               self.OnSearchMessageSearchButtonClick, self.SearchMessage)
-        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, 
+        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
               self.OnSearchMessageCancelButtonClick, self.SearchMessage)
         filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-        
-        self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"), 
+
+        self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
               size=wx.Size(28, 28), style=wx.NO_BORDER)
         self.CleanButton.SetToolTipString(_("Clean log messages"))
         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
         filter_sizer.AddWindow(self.CleanButton)
-        
+
         message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
         message_panel_sizer.AddGrowableCol(0)
         message_panel_sizer.AddGrowableRow(0)
         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
-        
+
         self.MessagePanel = wx.Panel(self)
         if wx.Platform == '__WXMSW__':
             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
@@ -337,45 +338,45 @@
         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
-        
+
         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
         message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
-        
+
         self.SetSizer(main_sizer)
-        
+
         self.LeftButtons = []
-        for label, callback in [("+" + text, self.GenerateOnDurationButton(duration)) 
+        for label, callback in [("+" + text, self.GenerateOnDurationButton(duration))
                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
             self.LeftButtons.append(LogButton(label, callback))
-        
+
         self.RightButtons = []
-        for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration)) 
+        for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration))
                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
             self.RightButtons.append(LogButton(label, callback))
-        
+
         self.MessageFilter.SetSelection(0)
         self.LogSource = None
         self.ResetLogMessages()
         self.ParentWindow = window
-    
+
         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
         self.CurrentFilter = self.LevelFilters[0]
         self.CurrentSearchValue = ""
-        
+
         self.ScrollSpeed = 0.
         self.LastStartTime = None
         self.ScrollTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
-        
+
         self.LastMousePos = None
         self.MessageToolTip = None
         self.MessageToolTipTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
-    
+
     def __del__(self):
         self.ScrollTimer.Stop()
-    
+
     def ResetLogMessages(self):
         self.previous_log_count = [None]*LogLevelsCount
         self.OldestMessages = []
@@ -383,14 +384,14 @@
         self.LogMessagesTimestamp = numpy.array([])
         self.CurrentMessage = None
         self.HasNewData = False
-    
+
     def SetLogSource(self, log_source):
-        self.LogSource = log_source
+        self.LogSource = proxy(log_source) if log_source else None
         self.CleanButton.Enable(self.LogSource is not None)
         if log_source is not None:
             self.ResetLogMessages()
             self.RefreshView()
-    
+
     def GetLogMessageFromSource(self, msgidx, level):
         if self.LogSource is not None:
             answer = self.LogSource.GetLogMessage(level, msgidx)
@@ -398,7 +399,7 @@
                 msg, tick, tv_sec, tv_nsec = answer
                 return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
         return None
-    
+
     def SetLogCounters(self, log_count):
         new_messages = []
         for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
@@ -441,12 +442,12 @@
                 self.MessageToolTipTimer.Stop()
                 self.ParentWindow.SelectTab(self)
             self.NewDataAvailable(None)
-    
+
     def FilterLogMessage(self, message, timestamp=None):
-        return (message.Level in self.CurrentFilter and 
+        return (message.Level in self.CurrentFilter and
                 message.Message.find(self.CurrentSearchValue) != -1 and
                 (timestamp is None or message.Timestamp < timestamp))
-    
+
     def GetMessageByTimestamp(self, timestamp):
         if self.CurrentMessage is not None:
             msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
@@ -455,7 +456,7 @@
                 return self.GetPreviousMessage(msgidx, timestamp)
             return message, msgidx
         return None, None
-    
+
     def GetNextMessage(self, msgidx):
         while msgidx < len(self.LogMessages) - 1:
             message = self.LogMessages[msgidx + 1]
@@ -463,7 +464,7 @@
                 return message, msgidx + 1
             msgidx += 1
         return None, None
-    
+
     def GetPreviousMessage(self, msgidx, timestamp=None):
         message = None
         while 0 < msgidx < len(self.LogMessages):
@@ -490,7 +491,7 @@
                     self.OldestMessages[level] = (-1, None)
                 if message is not None:
                     message_idx = 0
-                    while (message_idx < len(self.LogMessages) and 
+                    while (message_idx < len(self.LogMessages) and
                            self.LogMessages[message_idx] < message):
                         message_idx += 1
                     if len(self.LogMessages) > 0:
@@ -499,8 +500,8 @@
                         current_message = message
                     self.LogMessages.insert(message_idx, message)
                     self.LogMessagesTimestamp = numpy.insert(
-                            self.LogMessagesTimestamp, 
-                            [message_idx], 
+                            self.LogMessagesTimestamp,
+                            [message_idx],
                             [message.Timestamp])
                     self.CurrentMessage = self.LogMessages.index(current_message)
                     if message_idx == 0 and self.FilterLogMessage(message, timestamp):
@@ -509,27 +510,27 @@
                     if msg is not None and (message is None or msg > message):
                         message = msg
         return None, None
-    
+
     def RefreshNewData(self, *args, **kwargs):
         if self.HasNewData:
             self.HasNewData = False
             self.RefreshView()
         DebugViewer.RefreshNewData(self, *args, **kwargs)
-    
+
     def RefreshView(self):
         width, height = self.MessagePanel.GetClientSize()
         bitmap = wx.EmptyBitmap(width, height)
         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
         dc.Clear()
         dc.BeginDrawing()
-        
+
         if self.CurrentMessage is not None:
-            
+
             dc.SetFont(self.Font)
-            
+
             for button in self.LeftButtons + self.RightButtons:
                 button.Draw(dc)
-            
+
             message_idx = self.CurrentMessage
             message = self.LogMessages[message_idx]
             draw_date = True
@@ -537,23 +538,23 @@
             while offset < height and message is not None:
                 message.Draw(dc, offset, width, draw_date)
                 offset += message.GetHeight(draw_date)
-                
+
                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
                 if previous_message is not None:
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
-        
+
         dc.EndDrawing()
-        
+
         self.MessageScrollBar.RefreshThumbPosition()
-    
+
     def IsMessagePanelTop(self, message_idx=None):
         if message_idx is None:
             message_idx = self.CurrentMessage
         if message_idx is not None:
             return self.GetNextMessage(message_idx)[0] is None
         return True
-    
+
     def IsMessagePanelBottom(self, message_idx=None):
         if message_idx is None:
             message_idx = self.CurrentMessage
@@ -570,7 +571,7 @@
                 message = previous_message
             return offset < height
         return True
-    
+
     def ScrollMessagePanel(self, scroll):
         if self.CurrentMessage is not None:
             message = self.LogMessages[self.CurrentMessage]
@@ -585,13 +586,13 @@
                     self.CurrentMessage = msgidx
                     scroll += 1
             self.RefreshView()
-    
+
     def ScrollMessagePanelByPage(self, page):
         if self.CurrentMessage is not None:
             width, height = self.MessagePanel.GetClientSize()
             message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
             self.ScrollMessagePanel(page * message_per_page)
-    
+
     def ScrollMessagePanelByTimestamp(self, seconds):
         if self.CurrentMessage is not None:
             current_message = self.LogMessages[self.CurrentMessage]
@@ -603,7 +604,7 @@
                     msgidx += 1
                 self.CurrentMessage = msgidx
                 self.RefreshView()
-            
+
     def ResetMessagePanel(self):
         if len(self.LogMessages) > 0:
             self.CurrentMessage = len(self.LogMessages) - 1
@@ -611,45 +612,45 @@
             while message is not None and not self.FilterLogMessage(message):
                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
             self.RefreshView()
-    
+
     def OnMessageFilterChanged(self, event):
         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnSearchMessageChanged(self, event):
         self.CurrentSearchValue = self.SearchMessage.GetValue()
         self.ResetMessagePanel()
         event.Skip()
-        
+
     def OnSearchMessageSearchButtonClick(self, event):
         self.CurrentSearchValue = self.SearchMessage.GetValue()
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnSearchMessageCancelButtonClick(self, event):
         self.CurrentSearchValue = ""
         self.SearchMessage.SetValue("")
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnCleanButton(self, event):
         if self.LogSource is not None:
             self.LogSource.ResetLogCount()
         self.ResetLogMessages()
         self.RefreshView()
         event.Skip()
-    
+
     def GenerateOnDurationButton(self, duration):
         def OnDurationButton():
             self.ScrollMessagePanelByTimestamp(duration)
         return OnDurationButton
-    
+
     def GetCopyMessageToClipboardFunction(self, message):
         def CopyMessageToClipboardFunction(event):
             self.ParentWindow.SetCopyBuffer(message.GetFullText())
         return CopyMessageToClipboardFunction
-    
+
     def GetMessageByScreenPos(self, posx, posy):
         if self.CurrentMessage is not None:
             width, height = self.MessagePanel.GetClientSize()
@@ -657,22 +658,22 @@
             message = self.LogMessages[message_idx]
             draw_date = True
             offset = 5
-            
+
             while offset < height and message is not None:
                 if draw_date:
                     offset += DATE_INFO_SIZE
-    
+
                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
                     return message
-        
+
                 offset += MESSAGE_INFO_SIZE
-                
+
                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
                 if previous_message is not None:
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
         return None
-    
+
     def OnMessagePanelLeftUp(self, event):
         if self.CurrentMessage is not None:
             posx, posy = event.GetPosition()
@@ -681,32 +682,32 @@
                     button.ProcessCallback()
                     break
         event.Skip()
-    
+
     def OnMessagePanelRightUp(self, event):
         message = self.GetMessageByScreenPos(*event.GetPosition())
         if message is not None:
             menu = wx.Menu(title='')
-            
+
             new_id = wx.NewId()
             menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
-    
+
             self.MessagePanel.PopupMenu(menu)
             menu.Destroy()
         event.Skip()
-    
+
     def OnMessagePanelLeftDCLick(self, event):
         message = self.GetMessageByScreenPos(*event.GetPosition())
         if message is not None:
             self.SearchMessage.SetFocus()
             self.SearchMessage.SetValue(message.Message)
         event.Skip()
-    
+
     def ResetMessageToolTip(self):
         if self.MessageToolTip is not None:
             self.MessageToolTip.Destroy()
             self.MessageToolTip = None
-    
+
     def OnMessageToolTipTimer(self, event):
         if self.LastMousePos is not None:
             message = self.GetMessageByScreenPos(*self.LastMousePos)
@@ -719,31 +720,31 @@
                 self.MessageToolTip.SetToolTipPosition(tooltip_pos)
                 self.MessageToolTip.Show()
         event.Skip()
-    
+
     def OnMessagePanelMotion(self, event):
         if not event.Dragging():
             self.ResetMessageToolTip()
             self.LastMousePos = event.GetPosition()
             self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
         event.Skip()
-        
+
     def OnMessagePanelLeaveWindow(self, event):
         self.ResetMessageToolTip()
         self.LastMousePos = None
         self.MessageToolTipTimer.Stop()
         event.Skip()
-    
+
     def OnMessagePanelMouseWheel(self, event):
         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
         event.Skip()
-    
+
     def OnMessagePanelEraseBackground(self, event):
         pass
-    
+
     def OnMessagePanelPaint(self, event):
         self.RefreshView()
         event.Skip()
-    
+
     def OnMessagePanelResize(self, event):
         width, height = self.MessagePanel.GetClientSize()
         offset = 2
@@ -761,7 +762,7 @@
         else:
             self.RefreshView()
         event.Skip()
-    
+
     def OnScrollTimer(self, event):
         if self.ScrollSpeed != 0.:
             speed_norm = abs(self.ScrollSpeed)
@@ -770,7 +771,7 @@
             self.LastStartTime = gettime()
             self.ScrollTimer.Start(int(period * 1000), True)
         event.Skip()
-    
+
     def SetScrollSpeed(self, speed):
         if speed == 0.:
             self.ScrollTimer.Stop()
@@ -788,8 +789,8 @@
             else:
                 self.LastStartTime = current_time
             self.ScrollTimer.Start(int(period * 1000), True)
-        self.ScrollSpeed = speed    
-    
+        self.ScrollSpeed = speed
+
     def ScrollToLast(self, refresh=True):
         if len(self.LogMessages) > 0:
             self.CurrentMessage = len(self.LogMessages) - 1
--- a/runtime/WampClient.py	Sun Feb 08 16:50:54 2015 +0100
+++ b/runtime/WampClient.py	Sun Feb 08 22:39:17 2015 +0100
@@ -8,6 +8,7 @@
 from twisted.internet.defer import inlineCallbacks
 from autobahn.wamp import types
 from autobahn.wamp.serializer import MsgPackSerializer
+from twisted.internet.protocol import ReconnectingClientFactory
 import json
 
 _WampSession = None
@@ -23,6 +24,7 @@
                 "GetTraceVariables",
                 "RemoteExec",
                 "GetLogMessage",
+                "ResetLogCount",
                 ]
 
 def MakeCallee(name):
@@ -47,6 +49,14 @@
         _WampSession = None
         print 'WAMP session left'
 
+class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
+    def clientConnectionFailed(self, connector, reason):
+        print("WAMP Client connection failed .. retrying ..")
+        self.retry(connector)
+    def clientConnectionLost(self, connector, reason):
+        print("WAMP Client connection lost .. retrying ..")
+        self.retry(connector)
+
 def RegisterWampClient(wampconf):
 
     WSClientConf = json.load(open(wampconf))
@@ -63,7 +73,7 @@
     session_factory.session = WampSession
 
     # create a WAMP-over-WebSocket transport client factory
-    transport_factory = WampWebSocketClientFactory(
+    transport_factory = ReconnectingWampWebSocketClientFactory(
         session_factory,
         url = WSClientConf["url"],
         serializers = [MsgPackSerializer()],