--- a/Beremiz_service.py Thu Mar 07 21:57:18 2019 +0100
+++ b/Beremiz_service.py Mon Mar 11 01:03:32 2019 +0100
@@ -41,6 +41,7 @@
from runtime.xenomai import TryPreloadXenomai
from runtime import LogMessageAndException
from runtime import PlcStatus
+from runtime.Stunnel import ensurePSK
import util.paths as paths
@@ -415,7 +416,7 @@
reactor.registerWxApp(app)
twisted_reactor_thread_id = None
-ui_thread = None
+ui_thread = None
if havewx:
wx_eval_lock = Semaphore(0)
@@ -439,8 +440,8 @@
if ui_thread is not None \
and ui_thread.ident != current_id \
and (not havetwisted or (
- twisted_reactor_thread_id is not None
- and twisted_reactor_thread_id != current_id)):
+ twisted_reactor_thread_id is not None
+ and twisted_reactor_thread_id != current_id)):
o = type('', (object,), dict(call=(tocall, args, kwargs), res=None))
wx.CallAfter(wx_evaluator, o)
@@ -508,7 +509,6 @@
# Some extension may set 'servicename' to a computed ID or Serial Number
# instead of using commandline '-n'
if servicename is not None and PSKpath is not None:
- from runtime.Stunnel import ensurePSK
ensurePSK(servicename, PSKpath)
runtime.CreatePLCObjectSingleton(
--- a/PSKManagement.py Thu Mar 07 21:57:18 2019 +0100
+++ b/PSKManagement.py Mon Mar 11 01:03:32 2019 +0100
@@ -14,26 +14,32 @@
COL_ID, COL_URI, COL_DESC, COL_LAST = range(4)
REPLACE, REPLACE_ALL, KEEP, KEEP_ALL, CANCEL = range(5)
+
def _pskpath(project_path):
return os.path.join(project_path, 'psk')
+
def _mgtpath(project_path):
return os.path.join(_pskpath(project_path), 'management.json')
+
def _ensurePSKdir(project_path):
pskpath = _pskpath(project_path)
if not os.path.exists(pskpath):
os.mkdir(pskpath)
return pskpath
+
def _default(ID):
return [ID,
- '', # default description
- None, # last known URI
+ '', # default description
+ None, # last known URI
None] # last connection date
+
def _dataByID(data):
- return {row[COL_ID]:row for row in data}
+ return {row[COL_ID]: row for row in data}
+
def _LoadData(project_path):
""" load known keys metadata """
@@ -43,6 +49,7 @@
return json.loads(open(_path).read())
return []
+
def _filterData(psk_files, data_input):
input_by_ID = _dataByID(data_input)
output = []
@@ -51,11 +58,12 @@
# this implicitly filters IDs out of metadata who's
# secret is missing
for filename in psk_files:
- if filename.endswith('.secret'):
- ID = filename[:-7] # strip filename extension
- output.append(input_by_ID.get(ID,_default(ID)))
+ if filename.endswith('.secret'):
+ ID = filename[:-7] # strip filename extension
+ output.append(input_by_ID.get(ID, _default(ID)))
return output
+
def GetData(project_path):
loaded_data = _LoadData(project_path)
if loaded_data:
@@ -63,15 +71,18 @@
return _filterData(psk_files, loaded_data)
return []
+
def DeleteID(project_path, ID):
secret_path = os.path.join(_pskpath(project_path), ID+'.secret')
os.remove(secret_path)
+
def SaveData(project_path, data):
_ensurePSKdir(project_path)
with open(_mgtpath(project_path), 'w') as f:
f.write(json.dumps(data))
+
def UpdateID(project_path, ID, secret, URI):
pskpath = _ensurePSKdir(project_path)
if not os.path.exists(pskpath):
@@ -88,10 +99,10 @@
_is_new_ID = dataForID is None
if _is_new_ID:
- dataForID = _default(ID)
+ dataForID = _default(ID)
dataForID[COL_URI] = URI
- # FIXME : could store time instead os a string and use DVC model's cmp
+ # FIXME : could store time instead os a string and use DVC model's cmp
# then date display could be smarter, etc - sortable sting hack for now
dataForID[COL_LAST] = time.strftime('%y/%M/%d-%H:%M:%S')
@@ -100,6 +111,7 @@
SaveData(project_path, data)
+
def ExportIDs(project_path, export_zip):
with ZipFile(export_zip, 'w') as zf:
path = _pskpath(project_path)
@@ -107,6 +119,7 @@
if nm.endswith('.secret') or nm == 'management.json':
zf.write(os.path.join(path, nm), nm)
+
def ImportIDs(project_path, import_zip, should_I_replace_callback):
zf = ZipFile(import_zip, 'r')
data = GetData(project_path)
@@ -132,18 +145,16 @@
if result == CANCEL:
return
-
+
if result in [REPLACE_ALL, REPLACE]:
# replace with imported
existing_row[:] = imported_row
# copy the key of selected
keys_to_import.append(ID)
-
+
for ID in keys_to_import:
zf.extract(ID+".secret", _pskpath(project_path))
SaveData(project_path, data)
return data
-
-
--- a/ProjectController.py Thu Mar 07 21:57:18 2019 +0100
+++ b/ProjectController.py Mon Mar 11 01:03:32 2019 +0100
@@ -1789,8 +1789,8 @@
self._SetConnector(connectors.ConnectorFactory(uri, self))
except Exception as e:
self.logger.write_error(
- _("Exception while connecting to '%s': %s\n") % (uri, str(e)))
- #self.logger.write_error(traceback.format_exc())
+ _("Exception while connecting to '{uri}': {ex}\n").format(
+ uri=uri, ex=e))
# Did connection success ?
if self._connector is None:
@@ -1813,7 +1813,7 @@
if self._connector is None:
return
builder = self.GetBuilder()
- if builder is None :
+ if builder is None:
return
MD5 = builder.GetBinaryMD5()
if MD5 is None:
@@ -1846,7 +1846,7 @@
self.logger.write_error(_("Fatal : cannot get builder.\n"))
return False
- # recover md5 from last build
+ # recover md5 from last build
MD5 = builder.GetBinaryMD5()
# Check if md5 file is empty : ask user to build PLC
@@ -1871,11 +1871,11 @@
for name in os.listdir(extrafilespath):
extrafiles.append((
- name,
+ name,
self._connector.BlobFromFile(
# use file name as a seed to avoid collisions
# with files having same content
- os.path.join(extrafilespath, name),name)))
+ os.path.join(extrafilespath, name), name)))
# Send PLC on target
object_path = builder.GetBinaryPath()
--- a/connectors/ConnectorBase.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/ConnectorBase.py Mon Mar 11 01:03:32 2019 +0100
@@ -3,20 +3,22 @@
# See COPYING file for copyrights details.
+from __future__ import absolute_import
import md5
+
class ConnectorBase(object):
- #chuncksize = 16384
chuncksize = 1024*1024
+
def BlobFromFile(self, filepath, seed):
s = md5.new()
s.update(seed)
blobID = self.SeedBlob(seed)
with open(filepath, "rb") as f:
while blobID == s.digest():
- chunk = f.read(self.chuncksize)
- if len(chunk) == 0: return blobID
+ chunk = f.read(self.chuncksize)
+ if len(chunk) == 0:
+ return blobID
blobID = self.AppendChunkToBlob(chunk, blobID)
s.update(chunk)
-
--- a/connectors/PYRO/PSK_Adapter.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/PYRO/PSK_Adapter.py Mon Mar 11 01:03:32 2019 +0100
@@ -7,11 +7,11 @@
import ssl
import Pyro
from Pyro.core import PyroURI
-from Pyro.protocol import _connect_socket,TCPConnection,PYROAdapter
+from Pyro.protocol import _connect_socket, TCPConnection, PYROAdapter
from Pyro.errors import ConnectionDeniedError, ProtocolError
from Pyro.util import Log
-#
+
# The TLS-PSK adapter that handles SSL connections instead of regular sockets,
# but using Pre Shared Keys instead of Certificates
#
@@ -19,67 +19,76 @@
# This is essentialy the same as in Pyro/protocol.py
# only raw_sock wrapping into sock through sslpsk.wrap_socket was added
# Pyro unfortunately doesn't allow cleaner customization
- def bindToURI(self,URI):
+ def bindToURI(self, URI):
with self.lock: # only 1 thread at a time can bind the URI
try:
- self.URI=URI
+ self.URI = URI
# This are the statements that differ from Pyro/protocol.py
raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
_connect_socket(raw_sock, URI.address, URI.port, self.timeout)
sock = sslpsk.wrap_socket(
raw_sock, psk=Pyro.config.PYROPSK, server_side=False,
- ciphers="PSK-AES256-CBC-SHA", # available in openssl 1.0.2
+ ciphers="PSK-AES256-CBC-SHA", # available in openssl 1.0.2
ssl_version=ssl.PROTOCOL_TLSv1)
- # all the rest is the same as in Pyro/protocol.py
+ # all the rest is the same as in Pyro/protocol.py
- conn=TCPConnection(sock, sock.getpeername())
+ conn = TCPConnection(sock, sock.getpeername())
# receive the authentication challenge string, and use that to build the actual identification string.
try:
- authChallenge=self.recvAuthChallenge(conn)
- except ProtocolError,x:
+ authChallenge = self.recvAuthChallenge(conn)
+ except ProtocolError, x:
# check if we were denied
- if hasattr(x,"partialMsg") and x.partialMsg[:len(self.denyMSG)]==self.denyMSG:
+ if hasattr(x, "partialMsg") and x.partialMsg[:len(self.denyMSG)] == self.denyMSG:
raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(x.partialMsg[-1])])
else:
raise
# reply with our ident token, generated from the ident passphrase and the challenge
- msg = self._sendConnect(sock,self.newConnValidator.createAuthToken(self.ident, authChallenge, conn.addr, self.URI, None) )
- if msg==self.acceptMSG:
- self.conn=conn
- self.conn.connected=1
- Log.msg('PYROAdapter','connected to',str(URI))
- if URI.protocol=='PYROLOCPSK':
- self.resolvePYROLOC_URI("PYROPSK") # updates self.URI
- elif msg[:len(self.denyMSG)]==self.denyMSG:
+ msg = self._sendConnect(sock, self.newConnValidator.createAuthToken(self.ident, authChallenge, conn.addr, self.URI, None))
+ if msg == self.acceptMSG:
+ self.conn = conn
+ self.conn.connected = 1
+ Log.msg('PYROAdapter', 'connected to', str(URI))
+ if URI.protocol == 'PYROLOCPSK':
+ self.resolvePYROLOC_URI("PYROPSK") # updates self.URI
+ elif msg[:len(self.denyMSG)] == self.denyMSG:
try:
raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(msg[-1])])
- except (KeyError,ValueError):
+ except (KeyError, ValueError):
raise ConnectionDeniedError('invalid response')
except socket.error:
- Log.msg('PYROAdapter','connection failed to URI',str(URI))
+ Log.msg('PYROAdapter', 'connection failed to URI', str(URI))
raise ProtocolError('connection failed')
+
_getProtocolAdapter = Pyro.protocol.getProtocolAdapter
+
+
def getProtocolAdapter(protocol):
if protocol in ('PYROPSK', 'PYROLOCPSK'):
return PYROPSKAdapter()
return _getProtocolAdapter(protocol)
+
Pyro.protocol.getProtocolAdapter = getProtocolAdapter
+
_processStringURI = Pyro.core.processStringURI
+
+
def processStringURI(URI):
- x=re.match(r'(?P<protocol>PYROLOCPSK)://(?P<hostname>[^\s:]+):?(?P<port>\d+)?/(?P<name>\S*)',URI)
+ x = re.match(r'(?P<protocol>PYROLOCPSK)://(?P<hostname>[^\s:]+):?(?P<port>\d+)?/(?P<name>\S*)', URI)
if x:
- protocol=x.group('protocol')
- hostname=x.group('hostname')
- port=x.group('port')
+ protocol = x.group('protocol')
+ hostname = x.group('hostname')
+ port = x.group('port')
if port:
- port=int(port)
+ port = int(port)
else:
- port=0
- name=x.group('name')
- return PyroURI(hostname,name,port,protocol)
+ port = 0
+ name = x.group('name')
+ return PyroURI(hostname, name, port, protocol)
return _processStringURI(URI)
+
+
Pyro.core.processStringURI = processStringURI
--- a/connectors/PYRO/__init__.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/PYRO/__init__.py Mon Mar 11 01:03:32 2019 +0100
@@ -55,8 +55,9 @@
scheme, location = uri.split("://")
if scheme == "PYROS":
import connectors.PYRO.PSK_Adapter
+ _unused_module_imported_for_monkey_patching_pylint_sucks = connectors.PYRO.PSK_Adapter
schemename = "PYROLOCPSK"
- url, ID = location.split('#') #TODO fix exception when # not found
+ url, ID = location.split('#') # TODO fix exception when # not found
# load PSK from project
secpath = os.path.join(str(confnodesroot.ProjectPath), 'psk', ID+'.secret')
if not os.path.exists(secpath):
@@ -73,9 +74,10 @@
# Try to get the proxy object
try:
RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject")
- except Exception:
- confnodesroot.logger.write_error(_("Connection to '%s' failed with exception '%s'\n") % (location, str(e)))
- #confnodesroot.logger.write_error(traceback.format_exc())
+ except Exception, e:
+ confnodesroot.logger.write_error(
+ _("Connection to {loc} failed with exception {ex}\n").format(
+ loc=location, exo=str(e)))
return None
RemotePLCObjectProxy.adapter.setTimeout(60)
@@ -108,10 +110,9 @@
if IDPSK is None:
confnodesroot.logger.write_warning(_("PLC did not provide identity and security infomation.\n"))
else:
- ID,secret = IDPSK
+ ID, secret = IDPSK
PSK.UpdateID(confnodesroot.ProjectPath, ID, secret, uri)
-
_special_return_funcs = {
"StartPLC": False,
"GetTraceVariables": (PlcStatus.Broken, None),
--- a/connectors/PYRO_dialog.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/PYRO_dialog.py Mon Mar 11 01:03:32 2019 +0100
@@ -11,27 +11,28 @@
from connectors.SchemeEditor import SchemeEditor
-model = [('host',_("Host:")),
- ('port',_("Port:"))]
+model = [('host', _("Host:")),
+ ('port', _("Port:"))]
# (scheme, model, secure)
models = [("LOCAL", [], False), ("PYRO", model, False), ("PYROS", model, True)]
Schemes = list(zip(*models)[0])
-_PerSchemeConf = {sch : (mod,sec) for sch,mod,sec in models}
+_PerSchemeConf = {sch: (mod, sec) for sch, mod, sec in models}
+
class PYRO_dialog(SchemeEditor):
def __init__(self, scheme, *args, **kwargs):
-
# ID selector is enabled only on PYROS (secure)
self.model, self.EnableIDSelector = _PerSchemeConf[scheme]
SchemeEditor.__init__(self, scheme, *args, **kwargs)
+ # pylint: disable=unused-variable
def SetLoc(self, loc):
- hostport, ID = list(islice(chain(loc.split("#"), repeat("")),2))
- host, port = list(islice(chain(hostport.split(":"), repeat("")),2))
+ hostport, ID = list(islice(chain(loc.split("#"), repeat("")), 2))
+ host, port = list(islice(chain(hostport.split(":"), repeat("")), 2))
self.SetFields(locals())
def GetLoc(self):
@@ -39,10 +40,9 @@
fields = self.GetFields()
template = "{host}"
if fields['port']:
- template += ":{port}"
+ template += ":{port}"
if fields['ID']:
- template += "#{ID}"
+ template += "#{ID}"
return template.format(**fields)
return ''
-
--- a/connectors/SchemeEditor.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/SchemeEditor.py Mon Mar 11 01:03:32 2019 +0100
@@ -5,15 +5,15 @@
from __future__ import absolute_import
-from itertools import repeat, izip_longest
from functools import partial
import wx
from controls.IDBrowser import IDBrowser
+
class SchemeEditor(wx.Panel):
def __init__(self, scheme, parent, *args, **kwargs):
- self.txtctrls = {}
+ self.txtctrls = {}
wx.Panel.__init__(self, parent, *args, **kwargs)
self.fieldsizer = wx.FlexGridSizer(cols=2, hgap=10, vgap=10)
@@ -25,8 +25,9 @@
txtctrl = wx.TextCtrl(parent=self, size=wx.Size(200, -1))
self.txtctrls[tag] = txtctrl
for win, flag in [
- (wx.StaticText(self, label=label), wx.ALIGN_CENTER_VERTICAL),
- (txtctrl, wx.GROW)]:
+ (wx.StaticText(self, label=label),
+ wx.ALIGN_CENTER_VERTICAL),
+ (txtctrl, wx.GROW)]:
self.fieldsizer.AddWindow(win, flag=flag)
self.fieldsizer.AddSpacer(20)
@@ -45,9 +46,8 @@
self.SetSizer(self.fieldsizer)
def SetFields(self, fields):
- for tag, label in self.model:
+ for tag, _label in self.model:
self.txtctrls[tag].SetValue(fields[tag])
def GetFields(self):
- return {tag: self.txtctrls[tag].GetValue() for tag,label in self.model}
-
+ return {tag: self.txtctrls[tag].GetValue() for tag, _label in self.model}
--- a/connectors/WAMP/__init__.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/WAMP/__init__.py Mon Mar 11 01:03:32 2019 +0100
@@ -159,4 +159,5 @@
return WampPLCObjectProxy
+
WAMP_connector_factory = partial(_WAMP_connector_factory, WampSession)
--- a/connectors/WAMP_dialog.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/WAMP_dialog.py Mon Mar 11 01:03:32 2019 +0100
@@ -6,15 +6,15 @@
from __future__ import absolute_import
from itertools import repeat, islice, chain
-import wx
from connectors.SchemeEditor import SchemeEditor
Schemes = ["WAMP", "WAMPS"]
-model = [('host',_("Host:")),
- ('port',_("Port:")),
- ('realm',_("Realm:"))]
+model = [('host', _("Host:")),
+ ('port', _("Port:")),
+ ('realm', _("Realm:"))]
+
class WAMP_dialog(SchemeEditor):
def __init__(self, *args, **kwargs):
@@ -22,19 +22,19 @@
self.EnableIDSelector = True
SchemeEditor.__init__(self, *args, **kwargs)
+ # pylint: disable=unused-variable
def SetLoc(self, loc):
- hostport, realm, ID = list(islice(chain(loc.split("#"), repeat("")),3))
- host, port = list(islice(chain(hostport.split(":"), repeat("")),2))
+ hostport, realm, ID = list(islice(chain(loc.split("#"), repeat("")), 3))
+ host, port = list(islice(chain(hostport.split(":"), repeat("")), 2))
self.SetFields(locals())
def GetLoc(self):
fields = self.GetFields()
- #TODO : input validation test
+ # TODO : input validation test
template = "{host}" + \
(":{port}" if fields['port'] else '') +\
"#{realm}#{ID}"
return template.format(**fields)
-
--- a/connectors/__init__.py Thu Mar 07 21:57:18 2019 +0100
+++ b/connectors/__init__.py Mon Mar 11 01:03:32 2019 +0100
@@ -32,7 +32,7 @@
from connectors.ConnectorBase import ConnectorBase
from types import ClassType
-connectors_packages = ["PYRO","WAMP"]
+connectors_packages = ["PYRO", "WAMP"]
def _GetLocalConnectorClassFactory(name):
@@ -44,17 +44,18 @@
_dialogs_imported = False
per_URI_connectors = None
-schemes = None
+schemes = None
+
# lazy import of connectors dialogs, only if used
def _Import_Dialogs():
global per_URI_connectors, schemes, _dialogs_imported
- if not _dialogs_imported:
+ if not _dialogs_imported:
_dialogs_imported = True
per_URI_connectors = {}
schemes = []
for con_name in connectors_packages:
- module = __import__(con_name + '_dialog', globals(), locals())
+ module = __import__(con_name + '_dialog', globals(), locals())
for scheme in module.Schemes:
per_URI_connectors[scheme] = getattr(module, con_name + '_dialog')
@@ -110,20 +111,22 @@
return None
# import module according to uri type and get connector specific baseclass
- # first call to import the module,
+ # first call to import the module,
# then call with parameters to create the class
connector_specific_class = connectors[scheme]()(uri, confnodesroot)
-
+
if connector_specific_class is None:
return None
# new class inheriting from generic and specific connector base classes
- return ClassType(_scheme + "_connector",
+ return ClassType(_scheme + "_connector",
(ConnectorBase, connector_specific_class), {})()
+
def EditorClassFromScheme(scheme):
_Import_Dialogs()
- return per_URI_connectors.get(scheme, None)
+ return per_URI_connectors.get(scheme, None)
+
def ConnectorSchemes():
_Import_Dialogs()
--- a/controls/DiscoveryPanel.py Thu Mar 07 21:57:18 2019 +0100
+++ b/controls/DiscoveryPanel.py Mon Mar 11 01:03:32 2019 +0100
@@ -30,7 +30,6 @@
import wx
import wx.lib.mixins.listctrl as listmix
from zeroconf import ServiceBrowser, Zeroconf, get_all_addresses
-import netifaces
service_type = '_Beremiz._tcp.local.'
@@ -41,6 +40,7 @@
wx.ListCtrl.__init__(self, parent, id, pos, size, style, name=name)
listmix.ListCtrlAutoWidthMixin.__init__(self)
+
class DiscoveryPanel(wx.Panel, listmix.ColumnSorterMixin):
def _init_coll_MainSizer_Items(self, parent):
@@ -129,7 +129,7 @@
self.IfacesMonitorState = None
self.IfacesMonitorTimer = wx.Timer(self)
self.IfacesMonitorTimer.Start(2000)
- self.Bind(wx.EVT_TIMER, self.IfacesMonitor , self.IfacesMonitorTimer)
+ self.Bind(wx.EVT_TIMER, self.IfacesMonitor, self.IfacesMonitorTimer)
def __del__(self):
self.IfacesMonitorTimer.Stop()
@@ -139,7 +139,7 @@
def IfacesMonitor(self, event):
NewState = get_all_addresses(socket.AF_INET)
- if self.IfacesMonitorState != NewState:
+ if self.IfacesMonitorState != NewState:
if self.IfacesMonitorState is not None:
# refresh only if a new address appeared
for addr in NewState:
@@ -192,7 +192,7 @@
if self.LatestSelection is not None:
# if self.ByIPCheck.IsChecked():
svcname, scheme, host, port = \
- map(lambda col:self.getColumnText(self.LatestSelection, col),
+ map(lambda col: self.getColumnText(self.LatestSelection, col),
range(4))
return ("%s://%s:%s#%s" % (scheme, host, port, svcname)) \
if scheme[-1] == "S" \
--- a/controls/IDBrowser.py Thu Mar 07 21:57:18 2019 +0100
+++ b/controls/IDBrowser.py Mon Mar 11 01:03:32 2019 +0100
@@ -4,13 +4,13 @@
# See COPYING file for copyrights details.
from __future__ import absolute_import
-import os
import wx
import wx.dataview as dv
import PSKManagement as PSK
from PSKManagement import *
from dialogs.IDMergeDialog import IDMergeDialog
+
class IDBrowserModel(dv.PyDataViewIndexListModel):
def __init__(self, project_path, columncount):
self.project_path = project_path
@@ -36,9 +36,9 @@
def GetCount(self):
return len(self.data)
-
+
def Compare(self, item1, item2, col, ascending):
- if not ascending: # swap sort order?
+ if not ascending: # swap sort order?
item2, item1 = item1, item2
row1 = self.GetRow(item1)
row2 = self.GetRow(item2)
@@ -50,13 +50,13 @@
def DeleteRows(self, rows):
rows = list(rows)
rows.sort(reverse=True)
-
+
for row in rows:
PSK.DeleteID(self.project_path, self.data[row][COL_ID])
del self.data[row]
self.RowDeleted(row)
self._saveData()
-
+
def AddRow(self, value):
self.data.append(value)
self.RowAppended()
@@ -66,16 +66,18 @@
data = PSK.ImportIDs(self.project_path, filepath, sircb)
if data is not None:
self.data = data
- self.Reset(len(self.data))
+ self.Reset(len(self.data))
def Export(self, filepath):
PSK.ExportIDs(self.project_path, filepath)
-colflags = dv.DATAVIEW_COL_RESIZABLE|dv.DATAVIEW_COL_SORTABLE
+
+colflags = dv.DATAVIEW_COL_RESIZABLE | dv.DATAVIEW_COL_SORTABLE
+
class IDBrowser(wx.Panel):
def __init__(self, parent, ctr, SelectURICallBack=None, SelectIDCallBack=None, **kwargs):
- big = self.isManager = SelectURICallBack is None and SelectIDCallBack is None
+ big = self.isManager = SelectURICallBack is None and SelectIDCallBack is None
wx.Panel.__init__(self, parent, -1, size=(800 if big else 450,
600 if big else 200))
@@ -83,66 +85,66 @@
self.SelectIDCallBack = SelectIDCallBack
dvStyle = wx.BORDER_THEME | dv.DV_ROW_LINES
- if self.isManager :
+ if self.isManager:
# no multiple selection in selector mode
dvStyle |= dv.DV_MULTIPLE
- self.dvc = dv.DataViewCtrl(self, style = dvStyle)
-
- args = lambda *a,**k:(a,k)
+ self.dvc = dv.DataViewCtrl(self, style=dvStyle)
+
+ def args(*a, **k):
+ return (a, k)
ColumnsDesc = [
- args(_("ID"), COL_ID, width = 70),
- args(_("Last URI"), COL_URI, width = 300 if big else 80),
- args(_("Description"), COL_DESC, width = 300 if big else 200,
- mode = dv.DATAVIEW_CELL_EDITABLE
- if self.isManager
- else dv.DATAVIEW_CELL_INERT),
- args(_("Last connection"), COL_LAST, width = 120),
+ args(_("ID"), COL_ID, width=70),
+ args(_("Last URI"), COL_URI, width=300 if big else 80),
+ args(_("Description"), COL_DESC, width=300 if big else 200,
+ mode=dv.DATAVIEW_CELL_EDITABLE
+ if self.isManager
+ else dv.DATAVIEW_CELL_INERT),
+ args(_("Last connection"), COL_LAST, width=120),
]
self.model = IDBrowserModel(ctr.ProjectPath, len(ColumnsDesc))
self.dvc.AssociateModel(self.model)
col_list = []
- for a,k in ColumnsDesc:
+ for a, k in ColumnsDesc:
col_list.append(
- self.dvc.AppendTextColumn(*a,**dict(k, flags = colflags)))
+ self.dvc.AppendTextColumn(*a, **dict(k, flags=colflags)))
col_list[COL_LAST].SetSortOrder(False)
# TODO : sort by last bvisit by default
- self.Sizer = wx.BoxSizer(wx.VERTICAL)
+ self.Sizer = wx.BoxSizer(wx.VERTICAL)
self.Sizer.Add(self.dvc, 1, wx.EXPAND)
btnbox = wx.BoxSizer(wx.HORIZONTAL)
- if self.isManager :
+ if self.isManager:
# deletion of secret and metadata
deleteButton = wx.Button(self, label=_("Delete ID"))
self.Bind(wx.EVT_BUTTON, self.OnDeleteButton, deleteButton)
- btnbox.Add(deleteButton, 0, wx.LEFT|wx.RIGHT, 5)
+ btnbox.Add(deleteButton, 0, wx.LEFT | wx.RIGHT, 5)
# export all
exportButton = wx.Button(self, label=_("Export all"))
self.Bind(wx.EVT_BUTTON, self.OnExportButton, exportButton)
- btnbox.Add(exportButton, 0, wx.LEFT|wx.RIGHT, 5)
+ btnbox.Add(exportButton, 0, wx.LEFT | wx.RIGHT, 5)
# import with a merge -> duplicates are asked for
importButton = wx.Button(self, label=_("Import"))
self.Bind(wx.EVT_BUTTON, self.OnImportButton, importButton)
- btnbox.Add(importButton, 0, wx.LEFT|wx.RIGHT, 5)
-
- else :
+ btnbox.Add(importButton, 0, wx.LEFT | wx.RIGHT, 5)
+
+ else:
# selector mode
self.useURIButton = wx.Button(self, label=_("Use last URI"))
self.Bind(wx.EVT_BUTTON, self.OnUseURIButton, self.useURIButton)
self.useURIButton.Disable()
- btnbox.Add(self.useURIButton, 0, wx.LEFT|wx.RIGHT, 5)
-
- self.Sizer.Add(btnbox, 0, wx.TOP|wx.BOTTOM, 5)
+ btnbox.Add(self.useURIButton, 0, wx.LEFT | wx.RIGHT, 5)
+
+ self.Sizer.Add(btnbox, 0, wx.TOP | wx.BOTTOM, 5)
self.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.OnSelectionChanged, self.dvc)
-
def OnDeleteButton(self, evt):
items = self.dvc.GetSelections()
rows = [self.model.GetRow(item) for item in items]
@@ -150,13 +152,13 @@
# Ask if user really wants to delete
if wx.MessageBox(_('Are you sure to delete selected IDs?'),
_('Delete IDs'),
- wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT) != wx.YES:
+ wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT) != wx.YES:
return
self.model.DeleteRows(rows)
def OnSelectionChanged(self, evt):
- if not self.isManager :
+ if not self.isManager:
items = self.dvc.GetSelections()
somethingSelected = len(items) > 0
self.useURIButton.Enable(somethingSelected)
@@ -165,7 +167,6 @@
ID = self.model.GetValueByRow(row, COL_ID)
self.SelectIDCallBack(ID)
-
def OnUseURIButton(self, evt):
row = self.model.GetRow(self.dvc.GetSelections()[0])
URI = self.model.GetValueByRow(row, COL_URI)
@@ -174,16 +175,18 @@
def OnExportButton(self, evt):
dialog = wx.FileDialog(self, _("Choose a file"),
- wildcard = _("PSK ZIP files (*.zip)|*.zip"),
- style = wx.SAVE | wx.OVERWRITE_PROMPT)
+ wildcard=_("PSK ZIP files (*.zip)|*.zip"),
+ style=wx.SAVE | wx.OVERWRITE_PROMPT)
if dialog.ShowModal() == wx.ID_OK:
self.model.Export(dialog.GetPath())
- def ShouldIReplaceCallback(self,existing,replacement):
- ID,URI,DESC,LAST = existing
- _ID,_URI,_DESC,_LAST = replacement
- dlg = IDMergeDialog(self,
- _("Import IDs"),
+ # pylint: disable=unused-variable
+ def ShouldIReplaceCallback(self, existing, replacement):
+ ID, URI, DESC, LAST = existing
+ _ID, _URI, _DESC, _LAST = replacement
+ dlg = IDMergeDialog(
+ self,
+ _("Import IDs"),
(_("Replace information for ID {ID} ?") + "\n\n" +
_("Existing:") + "\n " +
_("Description:") + " {DESC}\n " +
@@ -194,9 +197,9 @@
_("Last known URI:") + " {_URI}\n " +
_("Last connection:") + " {_LAST}\n").format(**locals()),
_("Do the same for following IDs"),
- [_("Replace"), _("Keep"),_("Cancel")])
-
- answer = dlg.ShowModal() # return value ignored as we have "Ok" only anyhow
+ [_("Replace"), _("Keep"), _("Cancel")])
+
+ answer = dlg.ShowModal() # return value ignored as we have "Ok" only anyhow
if answer == wx.ID_CANCEL:
return CANCEL
@@ -211,9 +214,8 @@
def OnImportButton(self, evt):
dialog = wx.FileDialog(self, _("Choose a file"),
- wildcard = _("PSK ZIP files (*.zip)|*.zip"),
- style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
+ wildcard=_("PSK ZIP files (*.zip)|*.zip"),
+ style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if dialog.ShowModal() == wx.ID_OK:
self.model.Import(dialog.GetPath(),
self.ShouldIReplaceCallback)
-
--- a/controls/__init__.py Thu Mar 07 21:57:18 2019 +0100
+++ b/controls/__init__.py Mon Mar 11 01:03:32 2019 +0100
@@ -44,4 +44,3 @@
from controls.LogViewer import LogViewer
from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
from controls.CustomToolTip import CustomToolTip
-
--- a/dialogs/IDManager.py Thu Mar 07 21:57:18 2019 +0100
+++ b/dialogs/IDManager.py Mon Mar 11 01:03:32 2019 +0100
@@ -1,10 +1,9 @@
from __future__ import absolute_import
import wx
-from connectors import ConnectorSchemes, EditorClassFromScheme
-from controls.DiscoveryPanel import DiscoveryPanel
from controls.IDBrowser import IDBrowser
+
class IDManager(wx.Dialog):
def __init__(self, parent, ctr):
self.ctr = ctr
@@ -12,7 +11,7 @@
name='IDManager', parent=parent,
title=_('URI Editor'),
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
- size=(800,600))
+ size=(800, 600))
# start IDBrowser in manager mode
self.browser = IDBrowser(self, ctr)
self.Bind(wx.EVT_CHAR_HOOK, self.OnEscapeKey)
@@ -23,5 +22,3 @@
self.EndModal(wx.ID_CANCEL)
else:
event.Skip()
-
-
--- a/dialogs/IDMergeDialog.py Thu Mar 07 21:57:18 2019 +0100
+++ b/dialogs/IDMergeDialog.py Mon Mar 11 01:03:32 2019 +0100
@@ -6,7 +6,8 @@
from __future__ import absolute_import
import wx
-# class RichMessageDialog is still not available in wxPython 3.0.2
+
+# class RichMessageDialog is still not available in wxPython 3.0.2
class IDMergeDialog(wx.Dialog):
def __init__(self, parent, title, question, optiontext, button_texts):
wx.Dialog.__init__(self, parent, title=title)
@@ -15,17 +16,19 @@
message = wx.StaticText(self, label=question)
main_sizer.AddWindow(message, border=20,
- flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.LEFT | wx.RIGHT)
+ flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.LEFT | wx.RIGHT)
self.check = wx.CheckBox(self, label=optiontext)
main_sizer.AddWindow(self.check, border=20,
flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL)
buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
- for label,wxID in zip(button_texts, [wx.ID_YES, wx.ID_NO, wx.ID_CANCEL]):
+ for label, wxID in zip(button_texts, [wx.ID_YES, wx.ID_NO, wx.ID_CANCEL]):
Button = wx.Button(self, label=label)
+
def OnButtonFactory(_wxID):
return lambda event: self.EndModal(_wxID)
+
self.Bind(wx.EVT_BUTTON, OnButtonFactory(wxID), Button)
buttons_sizer.AddWindow(Button)
--- a/dialogs/UriEditor.py Thu Mar 07 21:57:18 2019 +0100
+++ b/dialogs/UriEditor.py Mon Mar 11 01:03:32 2019 +0100
@@ -4,6 +4,7 @@
from connectors import ConnectorSchemes, EditorClassFromScheme
from controls.DiscoveryPanel import DiscoveryPanel
+
class UriEditor(wx.Dialog):
def _init_ctrls(self, parent):
self.UriTypeChoice = wx.Choice(parent=self, choices=self.choices)
@@ -16,7 +17,7 @@
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
typeSizer = wx.BoxSizer(wx.HORIZONTAL)
typeSizer.Add(wx.StaticText(self, wx.ID_ANY, _("Scheme :")), border=5,
- flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
+ flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
typeSizer.Add(self.UriTypeChoice, border=5, flag=wx.ALL)
self.mainSizer.Add(typeSizer)
@@ -46,9 +47,9 @@
def SetURI(self, uri):
try:
- scheme, loc = uri.strip().split("://",1)
+ scheme, loc = uri.strip().split("://", 1)
scheme = scheme.upper()
- except:
+ except Exception:
scheme = None
if scheme in ConnectorSchemes():
@@ -62,7 +63,6 @@
if scheme is not None:
self.scheme_editor.SetLoc(loc)
-
def GetURI(self):
if self.scheme is None:
return self.scheme_editor.GetURI()
@@ -71,23 +71,22 @@
def _replaceSchemeEditor(self, scheme):
self.scheme = scheme
-
+
if self.scheme_editor is not None:
self.editor_sizer.Detach(self.scheme_editor)
self.scheme_editor.Destroy()
self.scheme_editor = None
- if scheme is not None :
+ if scheme is not None:
EditorClass = EditorClassFromScheme(scheme)
- self.scheme_editor = EditorClass(scheme,self)
- else :
+ self.scheme_editor = EditorClass(scheme, self)
+ else:
# None is for searching local network
- self.scheme_editor = DiscoveryPanel(self)
+ self.scheme_editor = DiscoveryPanel(self)
self.editor_sizer.Add(self.scheme_editor)
self.scheme_editor.Refresh()
-
+
self.editor_sizer.Layout()
self.mainSizer.Layout()
self.Fit()
-
--- a/dialogs/__init__.py Thu Mar 07 21:57:18 2019 +0100
+++ b/dialogs/__init__.py Mon Mar 11 01:03:32 2019 +0100
@@ -51,4 +51,3 @@
from dialogs.BrowseValuesLibraryDialog import BrowseValuesLibraryDialog
from dialogs.UriEditor import UriEditor
from dialogs.IDManager import IDManager
-
--- a/runtime/PLCObject.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/PLCObject.py Mon Mar 11 01:03:32 2019 +0100
@@ -30,14 +30,12 @@
import traceback
from time import time
import _ctypes # pylint: disable=wrong-import-order
+from six.moves import xrange
from past.builtins import execfile
-import Pyro.core as pyro
-import six
-from six.moves import _thread, xrange
import md5
from tempfile import mkstemp
import shutil
-from functools import wraps
+from functools import wraps, partial
from runtime.typemapping import TypeTranslator
from runtime.loglevels import LogLevelsDefault, LogLevelsCount
@@ -79,7 +77,7 @@
class PLCObject(object):
def __init__(self, WorkingDir, argv, statuschange, evaluator, pyruntimevars):
- self.workingdir = WorkingDir # must exits already
+ self.workingdir = WorkingDir # must exits already
self.tmpdir = os.path.join(WorkingDir, 'tmp')
if os.path.exists(self.tmpdir):
shutil.rmtree(self.tmpdir)
@@ -457,18 +455,18 @@
@RunInMain
def GetPLCID(self):
- return getPSKID()
+ return getPSKID(partial(self.LogMessage, 0))
def _init_blobs(self):
self.blobs = {}
if os.path.exists(self.tmpdir):
shutil.rmtree(self.tmpdir)
os.mkdir(self.tmpdir)
-
+
@RunInMain
def SeedBlob(self, seed):
blob = (mkstemp(dir=self.tmpdir) + (md5.new(),))
- fobj, path, md5sum = blob
+ _fobj, _path, md5sum = blob
md5sum.update(seed)
newBlobID = md5sum.digest()
self.blobs[newBlobID] = blob
@@ -481,17 +479,17 @@
if blob is None:
return None
- fobj, path, md5sum = blob
+ fobj, _path, md5sum = blob
md5sum.update(data)
newBlobID = md5sum.digest()
- os.write(fobj,data)
+ os.write(fobj, data)
self.blobs[newBlobID] = blob
return newBlobID
@RunInMain
def PurgeBlobs(self):
- for fobj, path, md5sum in self.blobs:
- os.close(fobj)
+ for fobj, _path, _md5sum in self.blobs:
+ os.close(fobj)
self._init_blobs()
def _BlobAsFile(self, blobID, newpath):
@@ -500,10 +498,10 @@
if blob is None:
raise Exception(_("Missing data to create file: {}").format(newpath))
- fobj, path, md5sum = blob
+ fobj, path, _md5sum = blob
os.close(fobj)
shutil.move(path, newpath)
-
+
@RunInMain
def NewPLC(self, md5sum, plc_object, extrafiles):
if self.PLCStatus in [PlcStatus.Stopped, PlcStatus.Empty, PlcStatus.Broken]:
@@ -608,8 +606,7 @@
@RunInMain
def GetTraceVariables(self, DebugToken):
- if (DebugToken is not None and
- DebugToken == self.DebugToken):
+ if (DebugToken is not None and DebugToken == self.DebugToken):
return self.PLCStatus, self._TracesSwap()
return PlcStatus.Broken, []
--- a/runtime/PyroServer.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/PyroServer.py Mon Mar 11 01:03:32 2019 +0100
@@ -41,7 +41,9 @@
sys.stdout.flush()
def PyroLoop(self, when_ready):
- if self._to_be_published(): self.Publish()
+ if self._to_be_published():
+ self.Publish()
+
while self.continueloop:
Pyro.config.PYRO_MULTITHREADED = 0
pyro.initServer()
--- a/runtime/ServicePublisher.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/ServicePublisher.py Mon Mar 11 01:03:32 2019 +0100
@@ -31,6 +31,7 @@
service_type = '_Beremiz._tcp.local.'
+
class ServicePublisher(object):
def __init__(self, protocol):
# type: fully qualified service type name
@@ -61,13 +62,13 @@
if ip == "0.0.0.0":
print("MDNS brodcasted on all interfaces")
- interfaces=zeroconf.InterfaceChoice.All
+ interfaces = zeroconf.InterfaceChoice.All
ip = self.gethostaddr()
else:
- interfaces=[ip]
+ interfaces = [ip]
self.server = zeroconf.Zeroconf(interfaces=interfaces)
-
+
print("MDNS brodcasted service address :" + ip)
self.ip_32b = socket.inet_aton(ip)
--- a/runtime/Stunnel.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/Stunnel.py Mon Mar 11 01:03:32 2019 +0100
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import os
from binascii import b2a_hqx
try:
@@ -5,14 +6,15 @@
except ImportError:
from subprocess import call
-restart_stunnel_cmdline = ["/etc/init.d/S50stunnel","restart"]
+restart_stunnel_cmdline = ["/etc/init.d/S50stunnel", "restart"]
_PSKpath = None
+
def PSKgen(ID, PSKpath):
# b2a_hqx output len is 4/3 input len
- secret = os.urandom(192) # int(256/1.3333)
+ secret = os.urandom(192) # int(256/1.3333)
secretstring = b2a_hqx(secret)
PSKstring = ID+":"+secretstring
@@ -20,6 +22,7 @@
f.write(PSKstring)
call(restart_stunnel_cmdline)
+
def ensurePSK(ID, PSKpath):
global _PSKpath
_PSKpath = PSKpath
@@ -28,14 +31,14 @@
# create if needed
PSKgen(ID, PSKpath)
-def getPSKID():
- if _PSKpath is not None :
+
+def getPSKID(errorlog):
+ if _PSKpath is not None:
if not os.path.exists(_PSKpath):
- confnodesroot.logger.write_error(
+ errorlog(
'Error: Pre-Shared-Key Secret in %s is missing!\n' % _PSKpath)
return None
- ID,_sep,PSK = open(_PSKpath).read().partition(':')
+ ID, _sep, PSK = open(_PSKpath).read().partition(':')
PSK = PSK.rstrip('\n\r')
- return (ID,PSK)
+ return (ID, PSK)
return None
-
--- a/runtime/Worker.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/Worker.py Mon Mar 11 01:03:32 2019 +0100
@@ -9,9 +9,9 @@
from __future__ import absolute_import
import sys
-import six
import thread
from threading import Lock, Condition
+import six
class job(object):
--- a/runtime/spawn_subprocess.py Thu Mar 07 21:57:18 2019 +0100
+++ b/runtime/spawn_subprocess.py Mon Mar 11 01:03:32 2019 +0100
@@ -3,14 +3,16 @@
# subset of subprocess built-in module using posix_spawn rather than fork.
-import posix_spawn
+from __future__ import absolute_import
import os
import signal
+import posix_spawn
PIPE = "42"
+
class Popen(object):
- def __init__(self, args,stdin=None, stdout=None):
+ def __init__(self, args, stdin=None, stdout=None):
self.returncode = None
self.stdout = None
self.stdin = None
@@ -40,7 +42,7 @@
def _wait(self):
if self.returncode is None:
- self.returncode = os.waitpid(self.pid,0)[1]
+ self.returncode = os.waitpid(self.pid, 0)[1]
def communicate(self):
if self.stdin is not None:
@@ -50,7 +52,7 @@
stdoutdata = self.stdout.read()
else:
stdoutdata = ""
-
+
# TODO
stderrdata = ""
@@ -58,7 +60,7 @@
if self.stdout is not None:
self.stdout.close()
self.stdout = None
-
+
return (stdoutdata, stderrdata)
def wait(self):
@@ -74,7 +76,7 @@
def poll(self):
if self.returncode is None:
pid, ret = os.waitpid(self.pid, os.WNOHANG)
- if (pid,ret) != (0,0):
+ if (pid, ret) != (0, 0):
self.returncode = ret
if self.stdin is not None:
@@ -85,7 +87,7 @@
self.stdout = None
return self.returncode
-
+
def kill(self):
os.kill(self.pid, signal.SIGKILL)
@@ -96,23 +98,23 @@
self.stdout.close()
self.stdout = None
-
def call(*args):
cmd = []
if isinstance(args[0], str):
- if len(args)==1:
+ if len(args) == 1:
# oversimplified splitting of arguments,
# TODO: care about use of simple and double quotes
cmd = args[0].split()
else:
cmd = args
- elif isinstance(args[0], list) and len(args)==1:
+ elif isinstance(args[0], list) and len(args) == 1:
cmd = args[0]
else:
raise Exception("Wrong arguments passed to subprocess.call")
pid = posix_spawn.posix_spawnp(cmd[0], cmd)
- return os.waitpid(pid,0)
+ return os.waitpid(pid, 0)
+
if __name__ == '__main__':
# unit test
@@ -120,12 +122,12 @@
p = Popen(["tr", "abc", "def"], stdin=PIPE, stdout=PIPE)
p.stdin.write("blah")
p.stdin.close()
- print p.stdout.read()
+ print(p.stdout.read())
p.wait()
p = Popen(["tr", "abc", "def"], stdin=PIPE, stdout=PIPE)
p.stdin.write("blah")
- print p.communicate()
+ print(p.communicate())
call("echo blah0")
call(["echo", "blah1"])
--- a/wxglade_hmi/wxglade_hmi.py Thu Mar 07 21:57:18 2019 +0100
+++ b/wxglade_hmi/wxglade_hmi.py Mon Mar 11 01:03:32 2019 +0100
@@ -136,24 +136,24 @@
[x["name"] for x in main_frames]) if len(main_frames) > 0 else "")
declare_hmi = \
-"\n".join(["%(name)s = None\n" % x for x in main_frames]) + \
-"\n".join(["\n".join(["%(class)s.%(h)s = %(h)s" %
- dict(x, h=h) for h in x['handlers']])
- for x in hmi_objects]) + """\
+ "\n".join(["%(name)s = None\n" % x for x in main_frames]) + \
+ "\n".join(["\n".join(["%(class)s.%(h)s = %(h)s" % dict(x, h=h)
+ for h in x['handlers']])
+ for x in hmi_objects]) + """\
def OnCloseFrame(evt):
wx.MessageBox(_("Please stop PLC to close"))
def InitHMI():
- """+ global_hmi + "\n" + "\n".join(["""\
+ """ + global_hmi + "\n" + "\n".join(["""\
%(name)s = %(class)s(None)
%(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
%(name)s.Show()
""" % x for x in main_frames]) + """\
def CleanupHMI():
- """+ global_hmi + "\n" + "\n".join(["""\
- if %(name)s is not None:
+ """ + global_hmi + "\n" + "\n".join(["""\
+ if %(name)s is not None:
%(name)s.Destroy()
""" % x for x in main_frames])