--- a/BeremizIDE.py Wed Jun 16 18:27:05 2021 +0200
+++ b/BeremizIDE.py Wed Jun 16 18:27:27 2021 +0200
@@ -223,7 +223,7 @@
def progress(self, text):
- l = self.output.GetLineCount()-2
+ l = max(self.output.GetLineCount()-2, 0) self.output.AnnotationSetText(l, text)
self.output.AnnotationSetVisible(wx.stc.STC_ANNOTATION_BOXED)
self.output.AnnotationSetStyle(l, self.black_white)
@@ -496,6 +496,7 @@
self.local_runtime_tmpdir = tempfile.mkdtemp()
# choose an arbitrary random port for runtime
self.runtime_port = int(random.random() * 1000) + 61131
+ self.Log.write(_("Starting local runtime...\n")) self.local_runtime = ProcessLogger(
--- a/ProjectController.py Wed Jun 16 18:27:05 2021 +0200
+++ b/ProjectController.py Wed Jun 16 18:27:27 2021 +0200
@@ -40,6 +40,7 @@
from datetime import datetime
from weakref import WeakKeyDictionary
from functools import reduce
+from itertools import izip from distutils.dir_util import copy_tree
from six.moves import xrange
@@ -1512,8 +1513,8 @@
for debug_tick, debug_buff in Traces:
debug_vars = UnpackDebugBuffer(
debug_buff, self.TracedIECTypes)
- if debug_vars is not None and len(debug_vars) == len(self.TracedIECPath):
- for IECPath, values_buffer, value in zip(
+ if debug_vars is not None: + for IECPath, values_buffer, value in izip( @@ -1606,8 +1607,8 @@
WeakKeyDictionary(), # Callables
[], # Data storage [(tick, data),...]
"Registered", # Variable status
- buffer_list] # Forced value
self.IECdebug_datas[IECPath] = IECdebug_data
IECdebug_data[4] |= buffer_list
@@ -1681,27 +1682,28 @@
return self._connector.RemoteExec(script, **kwargs)
def DispatchDebugValuesProc(self, event):
- self.debug_status, debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers()
- if len(self.TracedIECPath) == len(buffers):
- for IECPath, values in zip(self.TracedIECPath, buffers):
- self.CallWeakcallables(
- IECPath, "NewValues", debug_ticks, values)
- if len(debug_ticks) > 0:
- self.CallWeakcallables(
- "__tick__", "NewDataAvailable", debug_ticks)
+ self.debug_status, debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers() if self.debug_status == PlcStatus.Broken:
self.logger.write_warning(
_("Debug: token rejected - other debug took over - reconnect to recover\n"))
- delay = time.time() - start_time
- next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay)
- if self.DispatchDebugValuesTimer is not None:
- self.DispatchDebugValuesTimer.Start(
- int(next_refresh * 1000), oneShot=True)
+ for IECPath, values in zip(self.TracedIECPath, buffers): + self.CallWeakcallables( + IECPath, "NewValues", debug_ticks, values) + if len(debug_ticks) > 0: + self.CallWeakcallables( + "__tick__", "NewDataAvailable", debug_ticks) + delay = time.time() - start_time + next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay) + if self.DispatchDebugValuesTimer is not None: + res = self.DispatchDebugValuesTimer.Start( + int(next_refresh * 1000), oneShot=True) def KillDebugThread(self):
if self.DispatchDebugValuesTimer is not None:
--- a/controls/DebugVariablePanel/DebugVariableItem.py Wed Jun 16 18:27:05 2021 +0200
+++ b/controls/DebugVariablePanel/DebugVariableItem.py Wed Jun 16 18:27:27 2021 +0200
@@ -26,8 +26,9 @@
from __future__ import absolute_import
from datetime import timedelta
from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR
+from controls.DebugVariablePanel.RingBuffer import RingBuffer # -------------------------------------------------------------------------------
# Constant for calculate CRC for string variables
@@ -142,8 +143,8 @@
@return: Data as numpy.array([(tick, value, forced),...])
# Return immediately if data empty or none
- if self.Data is None or len(self.Data) == 0:
+ if self.Data is None or self.Data.count == 0: # Find nearest data outside given range indexes
start_idx = (self.GetNearestData(start_tick, -1)
@@ -154,7 +155,7 @@
# Return data between indexes
- return self.Data[start_idx:end_idx]
+ return self.Data.view[start_idx:end_idx] def GetRawValue(self, index):
@@ -195,8 +196,8 @@
# Return value range for data in given tick range
- data[numpy.argmin(values), 1],
- data[numpy.argmax(values), 1])
+ data[np.argmin(values), 1], + data[np.argmax(values), 1]) @@ -207,7 +208,7 @@
if self.StoreData and self.IsNumVariable():
# Init table storing data
- self.Data = numpy.array([]).reshape(0, 3)
+ self.Data = RingBuffer(3) # Init table storing raw data if variable is strin
@@ -294,7 +295,7 @@
[float(tick), num_value, extra_value])
# Add New data to stored data table
- self.Data = numpy.append(self.Data, data_values, axis=0)
+ self.Data.append(data_values) # Signal to debug variable panel to refresh
self.Parent.HasNewData = True
@@ -337,7 +338,7 @@
if tick is not None and self.Data is not None:
# Return current value and forced flag if data empty
- if len(self.Data) == 0:
+ if self.Data.count == 0: return self.Value, self.IsForced()
# Get index of nearest data from tick given
@@ -345,9 +346,9 @@
# Get value and forced flag at given index
- self.RawData[int(self.Data[idx, 2])] \
+ self.RawData[int(self.Data.view[idx, 2])] \ if self.VariableType in ["STRING", "WSTRING"] \
- else self.Data[idx, 1:3]
+ else self.Data.view[idx, 1:3] if self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
value = timedelta(seconds=value)
@@ -380,10 +381,10 @@
- ticks = self.Data[:, 0]
+ ticks = self.Data.view[:, 0] # Get nearest data from tick
- idx = numpy.argmin(abs(ticks - tick))
+ idx = min(np.searchsorted(ticks, tick), self.Data.count - 1) # Adjust data index according to constraint
if adjust < 0 and ticks[idx] > tick and idx > 0 or \
--- a/controls/DebugVariablePanel/DebugVariablePanel.py Wed Jun 16 18:27:05 2021 +0200
+++ b/controls/DebugVariablePanel/DebugVariablePanel.py Wed Jun 16 18:27:27 2021 +0200
@@ -26,7 +26,7 @@
from __future__ import absolute_import
from __future__ import division
from functools import reduce
@@ -43,6 +43,7 @@
from controls.DebugVariablePanel.DebugVariableItem import DebugVariableItem
from controls.DebugVariablePanel.DebugVariableTextViewer import DebugVariableTextViewer
from controls.DebugVariablePanel.DebugVariableGraphicViewer import *
+from controls.DebugVariablePanel.RingBuffer import RingBuffer MILLISECOND = 1000000 # Number of nanosecond in a millisecond
@@ -205,7 +206,7 @@
main_sizer = wx.BoxSizer(wx.VERTICAL)
- self.Ticks = numpy.array([]) # List of tick received
+ self.Ticks = RingBuffer() # List of tick received self.StartTick = 0 # Tick starting range of data displayed
self.Fixed = False # Flag that range of data is fixed
self.CursorTick = None # Tick of cursor for displaying values
@@ -344,11 +345,11 @@
# Save tick as start tick for range if data is still empty
- if len(self.Ticks) == 0:
+ if self.Ticks.count == 0: self.StartTick = ticks[0]
# Add tick to list of ticks received
- self.Ticks = numpy.append(self.Ticks, ticks)
+ self.Ticks.append(ticks) # Update start tick for range if range follow ticks received
if not self.Fixed or tick < self.StartTick + self.CurrentRange:
@@ -356,9 +357,13 @@
# Force refresh if graph is fixed because range of data received
# is too small to fill data range selected
- self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+ if self.Ticks.view[-1] - self.Ticks.view[0] < self.CurrentRange: + if self.Ticks.view[0] > self.StartTick: + self.StartTick = self.Ticks.view[0] @@ -385,17 +390,16 @@
def MoveCursorTick(self, move):
if self.CursorTick is not None:
- cursor_tick = max(self.Ticks[0],
- min(self.CursorTick + move, self.Ticks[-1]))
- cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
- if self.Ticks[cursor_tick_idx] == self.CursorTick:
+ cursor_tick = max(self.Ticks.view[0], + min(self.CursorTick + move, self.Ticks.view[-1])) + cursor_tick_idx = min(np.searchsorted(self.Ticks.view, cursor_tick), self.Ticks.count - 1) + if self.Ticks.view[cursor_tick_idx] == self.CursorTick: min(cursor_tick_idx + abs(move) // move,
- self.CursorTick = self.Ticks[cursor_tick_idx]
+ self.CursorTick = self.Ticks.view[cursor_tick_idx] - self.Ticks[numpy.argmin(
- numpy.abs(self.Ticks - self.CursorTick + self.CurrentRange))],
+ self.Ticks.view[min(np.searchsorted(self.Ticks.view, self.CursorTick - self.CurrentRange), self.Ticks.count - 1)], min(self.StartTick, self.CursorTick))
self.RefreshCanvasPosition()
@@ -547,8 +551,8 @@
if self.CursorTick is not None:
- elif len(self.Ticks) > 0:
+ elif self.Ticks.count > 0: + tick = self.Ticks.view[-1] @@ -604,16 +608,16 @@
self.RefreshGraphicsSizer()
def SetCanvasPosition(self, tick):
- tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
- self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
+ tick = max(self.Ticks.view[0], min(tick, self.Ticks.view[-1] - self.CurrentRange)) + self.StartTick = self.Ticks.view[min(np.searchsorted(self.Ticks.view, tick), self.Ticks.count - 1)] self.RefreshCanvasPosition()
def RefreshCanvasPosition(self):
- if len(self.Ticks) > 0:
- pos = int(self.StartTick - self.Ticks[0])
- range = int(self.Ticks[-1] - self.Ticks[0])
+ if len(self.Ticks.view) > 0: + pos = int(self.StartTick - self.Ticks.view[0]) + range = int(self.Ticks.view[-1] - self.Ticks.view[0]) @@ -626,23 +630,23 @@
if new_range_idx != current_range_idx:
self.CanvasRange.SetSelection(new_range_idx)
self.CurrentRange = self.RANGE_VALUES[new_range_idx][1] / self.Ticktime
- if len(self.Ticks) > 0:
+ if self.Ticks.count > 0: tick = self.StartTick + self.CurrentRange / 2.
new_start_tick = min(tick - (tick - self.StartTick) * self.CurrentRange / current_range,
- self.Ticks[-1] - self.CurrentRange)
- self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
- self.Fixed = new_start_tick < self.Ticks[-1] - self.CurrentRange
+ self.Ticks.view[-1] - self.CurrentRange) + self.StartTick = self.Ticks.view[min(np.searchsorted(self.Ticks.view, new_start_tick), self.Ticks.count - 1)] + self.Fixed = new_start_tick < self.Ticks.view[-1] - self.CurrentRange - if len(self.Ticks) > 0:
- if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+ if self.Ticks.count > 0: + if self.Fixed and self.Ticks.view[-1] - self.Ticks.view[0] < self.CurrentRange: - self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
+ self.StartTick = min(self.StartTick, self.Ticks.view[-1] - self.CurrentRange) - self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+ self.StartTick = max(self.Ticks.view[0], self.Ticks.view[-1] - self.CurrentRange) def OnRangeChanged(self, event):
@@ -654,8 +658,8 @@
def OnCurrentButton(self, event):
- if len(self.Ticks) > 0:
- self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+ if self.Ticks.count > 0: + self.StartTick = max(self.Ticks.view[0], self.Ticks.view[-1] - self.CurrentRange) @@ -695,8 +699,8 @@
def OnPositionChanging(self, event):
- if len(self.Ticks) > 0:
- self.StartTick = self.Ticks[0] + event.GetPosition()
+ if self.Ticks.count > 0: + self.StartTick = self.Ticks.view[0] + event.GetPosition() @@ -908,7 +912,7 @@
def ResetGraphicsValues(self):
- self.Ticks = numpy.array([])
+ self.Ticks = RingBuffer() for panel in self.GraphicPanels:
--- a/controls/DebugVariablePanel/DebugVariableTextViewer.py Wed Jun 16 18:27:05 2021 +0200
+++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py Wed Jun 16 18:27:27 2021 +0200
@@ -270,7 +270,7 @@
# Execute callback on button under mouse pointer if it exists
x, y = event.GetPosition()
- wx.CallAfter(self.HandleButton, x, y)
+ self.HandleButton(x, y) def OnLeftDClick(self, event):
--- a/runtime/typemapping.py Wed Jun 16 18:27:05 2021 +0200
+++ b/runtime/typemapping.py Wed Jun 16 18:27:27 2021 +0200
@@ -92,7 +92,7 @@
buffoffset += sizeof(c_type) if iectype != "STRING" else len(value)+1
if buffoffset and buffoffset == buffsize: