--- 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 @@
return False
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"))
# launch local runtime
self.local_runtime = ProcessLogger(
self.Log,
--- 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(
self.TracedIECPath,
self.DebugValuesBuffers,
debug_vars):
@@ -1606,8 +1607,8 @@
WeakKeyDictionary(), # Callables
[], # Data storage [(tick, data),...]
"Registered", # Variable status
- None,
- buffer_list] # Forced value
+ None, # Forced value
+ buffer_list]
self.IECdebug_datas[IECPath] = IECdebug_data
else:
IECdebug_data[4] |= buffer_list
@@ -1681,27 +1682,28 @@
return self._connector.RemoteExec(script, **kwargs)
def DispatchDebugValuesProc(self, event):
+ event.Skip()
+ start_time = time.time()
self.debug_status, debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers()
- start_time = time.time()
- if len(self.TracedIECPath) == len(buffers):
- for IECPath, values in zip(self.TracedIECPath, buffers):
- if len(values) > 0:
- self.CallWeakcallables(
- IECPath, "NewValues", debug_ticks, values)
- if len(debug_ticks) > 0:
- self.CallWeakcallables(
- "__tick__", "NewDataAvailable", debug_ticks)
if self.debug_status == PlcStatus.Broken:
self.logger.write_warning(
_("Debug: token rejected - other debug took over - reconnect to recover\n"))
- else:
- 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)
- event.Skip()
+ return
+
+ for IECPath, values in zip(self.TracedIECPath, buffers):
+ if len(values) > 0:
+ 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
import binascii
-import numpy
+import numpy as np
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:
- return self.Data
+ if self.Data is None or self.Data.count == 0:
+ return None
# Find nearest data outside given range indexes
start_idx = (self.GetNearestData(start_tick, -1)
@@ -154,7 +155,7 @@
else len(self.Data))
# 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 @@
if len(values) > 0:
# Return value range for data in given tick range
return (data,
- data[numpy.argmin(values), 1],
- data[numpy.argmax(values), 1])
+ data[np.argmin(values), 1],
+ data[np.argmax(values), 1])
# Return default values
return data, None, None
@@ -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
self.RawData = ([]
@@ -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
value, forced = \
- 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 @@
return None
# Extract data ticks
- 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
-import numpy
+import numpy as np
import wx
import wx.lib.buttons
@@ -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 @@
tick = ticks[-1]
# 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
- if self.Fixed and \
- self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
- self.Force = True
+ if self.Fixed :
+ if self.Ticks.view[-1] - self.Ticks.view[0] < self.CurrentRange:
+ self.Force = True
+ if self.Ticks.view[0] > self.StartTick:
+ self.StartTick = self.Ticks.view[0]
+ self.Force = True
+
self.HasNewData = False
self.RefreshView()
@@ -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:
cursor_tick_idx = max(0,
min(cursor_tick_idx + abs(move) // move,
- len(self.Ticks) - 1))
- self.CursorTick = self.Ticks[cursor_tick_idx]
+ self.Ticks.count - 1))
+ self.CursorTick = self.Ticks.view[cursor_tick_idx]
self.StartTick = max(
- 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()
self.UpdateCursorTick()
@@ -547,8 +551,8 @@
if self.CursorTick is not None:
tick = self.CursorTick
- elif len(self.Ticks) > 0:
- tick = self.Ticks[-1]
+ elif self.Ticks.count > 0:
+ tick = self.Ticks.view[-1]
else:
tick = None
if tick is not None:
@@ -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.Fixed = True
self.RefreshCanvasPosition()
self.ForceRefresh()
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])
else:
pos = 0
range = 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:
if tick is None:
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
self.ForceRefresh()
def RefreshRange(self):
- 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.Fixed = False
if self.Fixed:
- self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
- else:
- self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+ self.StartTick = min(self.StartTick, self.Ticks.view[-1] - self.CurrentRange)
+ else:
+ self.StartTick = max(self.Ticks.view[0], self.Ticks.view[-1] - self.CurrentRange)
self.ForceRefresh()
def OnRangeChanged(self, event):
@@ -654,8 +658,8 @@
event.Skip()
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)
self.ResetCursorTick()
event.Skip()
@@ -695,8 +699,8 @@
event.Skip()
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()
self.Fixed = True
self.ForceRefresh()
event.Skip()
@@ -908,7 +912,7 @@
self.ForceRefresh()
def ResetGraphicsValues(self):
- self.Ticks = numpy.array([])
+ self.Ticks = RingBuffer()
self.StartTick = 0
for panel in self.GraphicPanels:
panel.ResetItemsData()
--- 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)
event.Skip()
def OnLeftDClick(self, event):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/RingBuffer.py Wed Jun 16 18:27:27 2021 +0200
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2021: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+
+# Based on Eelco Hoogendoorn stackoverflow answer about RingBuffer with numpy
+
+import numpy as np
+
+
+class RingBuffer(object):
+ def __init__(self, width=None, size=131072, padding=None):
+ self.size = size
+ self.padding = size if padding is None else padding
+ shape = (self.size+self.padding,)
+ if width :
+ shape += (width,)
+ self.buffer = np.zeros(shape)
+ self.cursor = 0
+
+ def append(self, data):
+ """this is an O(n) operation"""
+ data = data[-self.size:]
+ n = len(data)
+ if self.size + self.padding - self.cursor < n:
+ self.compact()
+ self.buffer[self.cursor:][:n] = data
+ self.cursor += n
+
+ @property
+ def count(self):
+ return min(self.size, self.cursor)
+
+ @property
+ def view(self):
+ """this is always an O(1) operation"""
+ return self.buffer[max(0, self.cursor - self.size):][:self.count]
+
+ def compact(self):
+ """
+ note: only when this function is called, is an O(size) performance hit incurred,
+ and this cost is amortized over the whole padding space
+ """
+ print 'compacting'
+ self.buffer[:self.count] = self.view
+ self.cursor -= self.size
+
--- 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
res.append(value)
else:
- break
+ return None
if buffoffset and buffoffset == buffsize:
return res
return None