--- a/Beremiz.py Tue Aug 12 16:27:07 2008 +0200
+++ b/Beremiz.py Wed Aug 20 00:11:40 2008 +0200
@@ -357,13 +357,13 @@
# Add beremiz's icon in top left corner of the frame
self.SetIcon(wx.Icon(os.path.join(CWD, "images", "brz.ico"), wx.BITMAP_TYPE_ICO))
- self.PluginRoot = PluginsRoot(self)
+ self.PluginRoot = PluginsRoot(self, self.Log)
self.DisableEvents = False
self.PluginInfos = {}
if projectOpen:
- self.PluginRoot.LoadProject(projectOpen, self.Log)
+ self.PluginRoot.LoadProject(projectOpen)
self.RefreshPLCParams()
self.RefreshPluginTree()
@@ -572,7 +572,7 @@
else:
msizer = wx.FlexGridSizer(cols=1)
for plugin_method in plugin.PluginMethods:
- if "method" in plugin_method:
+ if "method" in plugin_method and plugin_method.get("shown",True):
id = wx.NewId()
button = GenBitmapTextButton(id=id, parent=parent,
bitmap=wx.Bitmap(os.path.join(CWD, "%s.png"%plugin_method.get("bitmap", os.path.join("images", "Unknown")))), label=plugin_method["name"],
@@ -675,7 +675,7 @@
enablebutton.SetBitmapSelected(wx.Bitmap(os.path.join(CWD, 'images', 'Enabled.png')))
enablebutton.SetToggle(plugin.MandatoryParams[1].getEnabled())
def toggleenablebutton(event):
- res = self.SetPluginParamsAttribute(plugin, "BaseParams.Enabled", enablebutton.GetToggle(), self.Log)
+ res = self.SetPluginParamsAttribute(plugin, "BaseParams.Enabled", enablebutton.GetToggle())
enablebutton.SetToggle(res)
event.Skip()
enablebutton.Bind(wx.EVT_BUTTON, toggleenablebutton, id=enablebutton_id)
@@ -895,7 +895,7 @@
def GetItemChannelChangedFunction(self, plugin, value):
def OnPluginTreeItemChannelChanged(event):
- res = self.SetPluginParamsAttribute(plugin, "BaseParams.IEC_Channel", value, self.Log)
+ res = self.SetPluginParamsAttribute(plugin, "BaseParams.IEC_Channel", value)
event.Skip()
return OnPluginTreeItemChannelChanged
@@ -923,7 +923,7 @@
# Disable button to prevent re-entrant call
event.GetEventObject().Disable()
# Call
- getattr(plugin,method)(self.Log)
+ getattr(plugin,method)()
# Re-enable button
event.GetEventObject().Enable()
# Trigger refresh on Idle
@@ -933,14 +933,14 @@
def GetChoiceCallBackFunction(self, choicectrl, plugin, path):
def OnChoiceChanged(event):
- res = self.SetPluginParamsAttribute(plugin, path, choicectrl.GetStringSelection(), self.Log)
+ res = self.SetPluginParamsAttribute(plugin, path, choicectrl.GetStringSelection())
choicectrl.SetStringSelection(res)
event.Skip()
return OnChoiceChanged
def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, plugin, path):
def OnChoiceContentChanged(event):
- res = self.SetPluginParamsAttribute(plugin, path, choicectrl.GetStringSelection(), self.Log)
+ res = self.SetPluginParamsAttribute(plugin, path, choicectrl.GetStringSelection())
if wx.VERSION < (2, 8, 0):
self.ParamsPanel.Freeze()
choicectrl.SetStringSelection(res)
@@ -958,14 +958,14 @@
def GetTextCtrlCallBackFunction(self, textctrl, plugin, path):
def OnTextCtrlChanged(event):
- res = self.SetPluginParamsAttribute(plugin, path, textctrl.GetValue(), self.Log)
+ res = self.SetPluginParamsAttribute(plugin, path, textctrl.GetValue())
textctrl.SetValue(res)
event.Skip()
return OnTextCtrlChanged
def GetCheckBoxCallBackFunction(self, chkbx, plugin, path):
def OnCheckBoxChanged(event):
- res = self.SetPluginParamsAttribute(plugin, path, chkbx.IsChecked(), self.Log)
+ res = self.SetPluginParamsAttribute(plugin, path, chkbx.IsChecked())
chkbx.SetValue(res)
event.Skip()
return OnCheckBoxChanged
@@ -1112,7 +1112,7 @@
if dialog.ShowModal() == wx.ID_OK:
projectpath = dialog.GetPath()
if os.path.isdir(projectpath):
- result = self.PluginRoot.LoadProject(projectpath, self.Log)
+ result = self.PluginRoot.LoadProject(projectpath)
if not result:
self.RefreshPLCParams()
self.RefreshPluginTree()
@@ -1216,7 +1216,7 @@
dialog = wx.TextEntryDialog(self, "Please enter a name for plugin:", "Add Plugin", "", wx.OK|wx.CANCEL)
if dialog.ShowModal() == wx.ID_OK:
PluginName = dialog.GetValue()
- plugin.PlugAddChild(PluginName, PluginType, self.Log)
+ plugin.PlugAddChild(PluginName, PluginType)
self.RefreshPluginTree()
dialog.Destroy()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Beremiz_service.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#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 os, sys, getopt, socket
+
+def usage():
+ print "\nUsage of Beremiz PLC execution service :"
+ print "\n %s [PLC path]\n"%sys.argv[0]
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
+except getopt.GetoptError:
+ # print help information and exit:
+ usage()
+ sys.exit(2)
+
+for o, a in opts:
+ if o in ("-h", "--help"):
+ usage()
+ sys.exit()
+
+if len(args) > 1:
+ usage()
+ sys.exit()
+elif len(args) == 1:
+ WorkingDir = args[0]
+elif len(args) == 0:
+ WorkingDir = os.getcwd()
+else:
+ usage()
+ sys.exit()
+
+from runtime import PLCObject, ServicePublisher
+import Pyro.core as pyro
+
+if not os.path.isdir(WorkingDir):
+ os.mkdir(WorkingDir)
+
+# type: fully qualified service type name
+type = '_PYRO._tcp.local.'
+# name: fully qualified service name
+name = 'First test.%s'%(type)
+# address: IP address as unsigned short, network byte order
+
+def gethostaddr(dst = '224.0.1.41'):
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ s.connect((dst, 7))
+ (host, port) = s.getsockname()
+ s.close()
+ if host != '0.0.0.0':
+ return host
+ except error:
+ pass
+ return socket.gethostbyname(socket.gethostname())
+
+ip = gethostaddr()
+# port: port that the service runs on
+port = 3000
+# properties: dictionary of properties (or a string holding the bytes for the text field)
+serviceproperties = {'description':'Remote control for PLC'}
+
+pyro.initServer()
+daemon=pyro.Daemon(host=ip, port=port)
+uri = daemon.connect(PLCObject(WorkingDir, daemon),"PLCObject")
+print "The daemon runs on port :",daemon.port
+print "The object's uri is :",uri
+print "The working directory :",WorkingDir
+print "Publish service on local network"
+
+ip_32b = socket.inet_aton(ip)
+# Configure and publish service
+service = ServicePublisher.PublishService()
+service.ConfigureService(type, name, ip_32b, port, serviceproperties)
+service.PublishService()
+
+daemon.requestLoop()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Zeroconf.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,1556 @@
+""" Multicast DNS Service Discovery for Python, v0.12
+ Copyright (C) 2003, Paul Scott-Murphy
+
+ This module provides a framework for the use of DNS Service Discovery
+ using IP multicast. It has been tested against the JRendezvous
+ implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
+ and against the mDNSResponder from Mac OS X 10.3.8.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+"""0.12 update - allow selection of binding interface
+ typo fix - Thanks A. M. Kuchlingi
+ removed all use of word 'Rendezvous' - this is an API change"""
+
+"""0.11 update - correction to comments for addListener method
+ support for new record types seen from OS X
+ - IPv6 address
+ - hostinfo
+ ignore unknown DNS record types
+ fixes to name decoding
+ works alongside other processes using port 5353 (e.g. on Mac OS X)
+ tested against Mac OS X 10.3.2's mDNSResponder
+ corrections to removal of list entries for service browser"""
+
+"""0.10 update - Jonathon Paisley contributed these corrections:
+ always multicast replies, even when query is unicast
+ correct a pointer encoding problem
+ can now write records in any order
+ traceback shown on failure
+ better TXT record parsing
+ server is now separate from name
+ can cancel a service browser
+
+ modified some unit tests to accommodate these changes"""
+
+"""0.09 update - remove all records on service unregistration
+ fix DOS security problem with readName"""
+
+"""0.08 update - changed licensing to LGPL"""
+
+"""0.07 update - faster shutdown on engine
+ pointer encoding of outgoing names
+ ServiceBrowser now works
+ new unit tests"""
+
+"""0.06 update - small improvements with unit tests
+ added defined exception types
+ new style objects
+ fixed hostname/interface problem
+ fixed socket timeout problem
+ fixed addServiceListener() typo bug
+ using select() for socket reads
+ tested on Debian unstable with Python 2.2.2"""
+
+"""0.05 update - ensure case insensitivty on domain names
+ support for unicast DNS queries"""
+
+"""0.04 update - added some unit tests
+ added __ne__ adjuncts where required
+ ensure names end in '.local.'
+ timeout on receiving socket for clean shutdown"""
+
+__author__ = "Paul Scott-Murphy"
+__email__ = "paul at scott dash murphy dot com"
+__version__ = "0.12"
+
+import string
+import time
+import struct
+import socket
+import threading
+import select
+import traceback
+
+__all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
+
+# hook for threads
+
+globals()['_GLOBAL_DONE'] = 0
+
+# Some timing constants
+
+_UNREGISTER_TIME = 125
+_CHECK_TIME = 175
+_REGISTER_TIME = 225
+_LISTENER_TIME = 200
+_BROWSER_TIME = 500
+
+# Some DNS constants
+
+_MDNS_ADDR = '224.0.0.251'
+_MDNS_PORT = 5353;
+_DNS_PORT = 53;
+_DNS_TTL = 60 * 60; # one hour default TTL
+
+_MAX_MSG_TYPICAL = 1460 # unused
+_MAX_MSG_ABSOLUTE = 8972
+
+_FLAGS_QR_MASK = 0x8000 # query response mask
+_FLAGS_QR_QUERY = 0x0000 # query
+_FLAGS_QR_RESPONSE = 0x8000 # response
+
+_FLAGS_AA = 0x0400 # Authorative answer
+_FLAGS_TC = 0x0200 # Truncated
+_FLAGS_RD = 0x0100 # Recursion desired
+_FLAGS_RA = 0x8000 # Recursion available
+
+_FLAGS_Z = 0x0040 # Zero
+_FLAGS_AD = 0x0020 # Authentic data
+_FLAGS_CD = 0x0010 # Checking disabled
+
+_CLASS_IN = 1
+_CLASS_CS = 2
+_CLASS_CH = 3
+_CLASS_HS = 4
+_CLASS_NONE = 254
+_CLASS_ANY = 255
+_CLASS_MASK = 0x7FFF
+_CLASS_UNIQUE = 0x8000
+
+_TYPE_A = 1
+_TYPE_NS = 2
+_TYPE_MD = 3
+_TYPE_MF = 4
+_TYPE_CNAME = 5
+_TYPE_SOA = 6
+_TYPE_MB = 7
+_TYPE_MG = 8
+_TYPE_MR = 9
+_TYPE_NULL = 10
+_TYPE_WKS = 11
+_TYPE_PTR = 12
+_TYPE_HINFO = 13
+_TYPE_MINFO = 14
+_TYPE_MX = 15
+_TYPE_TXT = 16
+_TYPE_AAAA = 28
+_TYPE_SRV = 33
+_TYPE_ANY = 255
+
+# Mapping constants to names
+
+_CLASSES = { _CLASS_IN : "in",
+ _CLASS_CS : "cs",
+ _CLASS_CH : "ch",
+ _CLASS_HS : "hs",
+ _CLASS_NONE : "none",
+ _CLASS_ANY : "any" }
+
+_TYPES = { _TYPE_A : "a",
+ _TYPE_NS : "ns",
+ _TYPE_MD : "md",
+ _TYPE_MF : "mf",
+ _TYPE_CNAME : "cname",
+ _TYPE_SOA : "soa",
+ _TYPE_MB : "mb",
+ _TYPE_MG : "mg",
+ _TYPE_MR : "mr",
+ _TYPE_NULL : "null",
+ _TYPE_WKS : "wks",
+ _TYPE_PTR : "ptr",
+ _TYPE_HINFO : "hinfo",
+ _TYPE_MINFO : "minfo",
+ _TYPE_MX : "mx",
+ _TYPE_TXT : "txt",
+ _TYPE_AAAA : "quada",
+ _TYPE_SRV : "srv",
+ _TYPE_ANY : "any" }
+
+# utility functions
+
+def currentTimeMillis():
+ """Current system time in milliseconds"""
+ return time.time() * 1000
+
+# Exceptions
+
+class NonLocalNameException(Exception):
+ pass
+
+class NonUniqueNameException(Exception):
+ pass
+
+class NamePartTooLongException(Exception):
+ pass
+
+class AbstractMethodException(Exception):
+ pass
+
+class BadTypeInNameException(Exception):
+ pass
+
+# implementation classes
+
+class DNSEntry(object):
+ """A DNS entry"""
+
+ def __init__(self, name, type, clazz):
+ self.key = string.lower(name)
+ self.name = name
+ self.type = type
+ self.clazz = clazz & _CLASS_MASK
+ self.unique = (clazz & _CLASS_UNIQUE) != 0
+
+ def __eq__(self, other):
+ """Equality test on name, type, and class"""
+ if isinstance(other, DNSEntry):
+ return self.name == other.name and self.type == other.type and self.clazz == other.clazz
+ return 0
+
+ def __ne__(self, other):
+ """Non-equality test"""
+ return not self.__eq__(other)
+
+ def getClazz(self, clazz):
+ """Class accessor"""
+ try:
+ return _CLASSES[clazz]
+ except:
+ return "?(%s)" % (clazz)
+
+ def getType(self, type):
+ """Type accessor"""
+ try:
+ return _TYPES[type]
+ except:
+ return "?(%s)" % (type)
+
+ def toString(self, hdr, other):
+ """String representation with additional information"""
+ result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
+ if self.unique:
+ result += "-unique,"
+ else:
+ result += ","
+ result += self.name
+ if other is not None:
+ result += ",%s]" % (other)
+ else:
+ result += "]"
+ return result
+
+class DNSQuestion(DNSEntry):
+ """A DNS question entry"""
+
+ def __init__(self, name, type, clazz):
+ if not name.endswith(".local."):
+ raise NonLocalNameException
+ DNSEntry.__init__(self, name, type, clazz)
+
+ def answeredBy(self, rec):
+ """Returns true if the question is answered by the record"""
+ return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
+
+ def __repr__(self):
+ """String representation"""
+ return DNSEntry.toString(self, "question", None)
+
+
+class DNSRecord(DNSEntry):
+ """A DNS record - like a DNS entry, but has a TTL"""
+
+ def __init__(self, name, type, clazz, ttl):
+ DNSEntry.__init__(self, name, type, clazz)
+ self.ttl = ttl
+ self.created = currentTimeMillis()
+
+ def __eq__(self, other):
+ """Tests equality as per DNSRecord"""
+ if isinstance(other, DNSRecord):
+ return DNSEntry.__eq__(self, other)
+ return 0
+
+ def suppressedBy(self, msg):
+ """Returns true if any answer in a message can suffice for the
+ information held in this record."""
+ for record in msg.answers:
+ if self.suppressedByAnswer(record):
+ return 1
+ return 0
+
+ def suppressedByAnswer(self, other):
+ """Returns true if another record has same name, type and class,
+ and if its TTL is at least half of this record's."""
+ if self == other and other.ttl > (self.ttl / 2):
+ return 1
+ return 0
+
+ def getExpirationTime(self, percent):
+ """Returns the time at which this record will have expired
+ by a certain percentage."""
+ return self.created + (percent * self.ttl * 10)
+
+ def getRemainingTTL(self, now):
+ """Returns the remaining TTL in seconds."""
+ return max(0, (self.getExpirationTime(100) - now) / 1000)
+
+ def isExpired(self, now):
+ """Returns true if this record has expired."""
+ return self.getExpirationTime(100) <= now
+
+ def isStale(self, now):
+ """Returns true if this record is at least half way expired."""
+ return self.getExpirationTime(50) <= now
+
+ def resetTTL(self, other):
+ """Sets this record's TTL and created time to that of
+ another record."""
+ self.created = other.created
+ self.ttl = other.ttl
+
+ def write(self, out):
+ """Abstract method"""
+ raise AbstractMethodException
+
+ def toString(self, other):
+ """String representation with addtional information"""
+ arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
+ return DNSEntry.toString(self, "record", arg)
+
+class DNSAddress(DNSRecord):
+ """A DNS address record"""
+
+ def __init__(self, name, type, clazz, ttl, address):
+ DNSRecord.__init__(self, name, type, clazz, ttl)
+ self.address = address
+
+ def write(self, out):
+ """Used in constructing an outgoing packet"""
+ out.writeString(self.address, len(self.address))
+
+ def __eq__(self, other):
+ """Tests equality on address"""
+ if isinstance(other, DNSAddress):
+ return self.address == other.address
+ return 0
+
+ def __repr__(self):
+ """String representation"""
+ try:
+ return socket.inet_ntoa(self.address)
+ except:
+ return self.address
+
+class DNSHinfo(DNSRecord):
+ """A DNS host information record"""
+
+ def __init__(self, name, type, clazz, ttl, cpu, os):
+ DNSRecord.__init__(self, name, type, clazz, ttl)
+ self.cpu = cpu
+ self.os = os
+
+ def write(self, out):
+ """Used in constructing an outgoing packet"""
+ out.writeString(self.cpu, len(self.cpu))
+ out.writeString(self.os, len(self.os))
+
+ def __eq__(self, other):
+ """Tests equality on cpu and os"""
+ if isinstance(other, DNSHinfo):
+ return self.cpu == other.cpu and self.os == other.os
+ return 0
+
+ def __repr__(self):
+ """String representation"""
+ return self.cpu + " " + self.os
+
+class DNSPointer(DNSRecord):
+ """A DNS pointer record"""
+
+ def __init__(self, name, type, clazz, ttl, alias):
+ DNSRecord.__init__(self, name, type, clazz, ttl)
+ self.alias = alias
+
+ def write(self, out):
+ """Used in constructing an outgoing packet"""
+ out.writeName(self.alias)
+
+ def __eq__(self, other):
+ """Tests equality on alias"""
+ if isinstance(other, DNSPointer):
+ return self.alias == other.alias
+ return 0
+
+ def __repr__(self):
+ """String representation"""
+ return self.toString(self.alias)
+
+class DNSText(DNSRecord):
+ """A DNS text record"""
+
+ def __init__(self, name, type, clazz, ttl, text):
+ DNSRecord.__init__(self, name, type, clazz, ttl)
+ self.text = text
+
+ def write(self, out):
+ """Used in constructing an outgoing packet"""
+ out.writeString(self.text, len(self.text))
+
+ def __eq__(self, other):
+ """Tests equality on text"""
+ if isinstance(other, DNSText):
+ return self.text == other.text
+ return 0
+
+ def __repr__(self):
+ """String representation"""
+ if len(self.text) > 10:
+ return self.toString(self.text[:7] + "...")
+ else:
+ return self.toString(self.text)
+
+class DNSService(DNSRecord):
+ """A DNS service record"""
+
+ def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
+ DNSRecord.__init__(self, name, type, clazz, ttl)
+ self.priority = priority
+ self.weight = weight
+ self.port = port
+ self.server = server
+
+ def write(self, out):
+ """Used in constructing an outgoing packet"""
+ out.writeShort(self.priority)
+ out.writeShort(self.weight)
+ out.writeShort(self.port)
+ out.writeName(self.server)
+
+ def __eq__(self, other):
+ """Tests equality on priority, weight, port and server"""
+ if isinstance(other, DNSService):
+ return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
+ return 0
+
+ def __repr__(self):
+ """String representation"""
+ return self.toString("%s:%s" % (self.server, self.port))
+
+class DNSIncoming(object):
+ """Object representation of an incoming DNS packet"""
+
+ def __init__(self, data):
+ """Constructor from string holding bytes of packet"""
+ self.offset = 0
+ self.data = data
+ self.questions = []
+ self.answers = []
+ self.numQuestions = 0
+ self.numAnswers = 0
+ self.numAuthorities = 0
+ self.numAdditionals = 0
+
+ self.readHeader()
+ self.readQuestions()
+ self.readOthers()
+
+ def readHeader(self):
+ """Reads header portion of packet"""
+ format = '!HHHHHH'
+ length = struct.calcsize(format)
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+
+ self.id = info[0]
+ self.flags = info[1]
+ self.numQuestions = info[2]
+ self.numAnswers = info[3]
+ self.numAuthorities = info[4]
+ self.numAdditionals = info[5]
+
+ def readQuestions(self):
+ """Reads questions section of packet"""
+ format = '!HH'
+ length = struct.calcsize(format)
+ for i in range(0, self.numQuestions):
+ name = self.readName()
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+
+ question = DNSQuestion(name, info[0], info[1])
+ self.questions.append(question)
+
+ def readInt(self):
+ """Reads an integer from the packet"""
+ format = '!I'
+ length = struct.calcsize(format)
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+ return info[0]
+
+ def readCharacterString(self):
+ """Reads a character string from the packet"""
+ length = ord(self.data[self.offset])
+ self.offset += 1
+ return self.readString(length)
+
+ def readString(self, len):
+ """Reads a string of a given length from the packet"""
+ format = '!' + str(len) + 's'
+ length = struct.calcsize(format)
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+ return info[0]
+
+ def readUnsignedShort(self):
+ """Reads an unsigned short from the packet"""
+ format = '!H'
+ length = struct.calcsize(format)
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+ return info[0]
+
+ def readOthers(self):
+ """Reads the answers, authorities and additionals section of the packet"""
+ format = '!HHiH'
+ length = struct.calcsize(format)
+ n = self.numAnswers + self.numAuthorities + self.numAdditionals
+ for i in range(0, n):
+ domain = self.readName()
+ info = struct.unpack(format, self.data[self.offset:self.offset+length])
+ self.offset += length
+
+ rec = None
+ if info[0] == _TYPE_A:
+ rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
+ elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
+ rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
+ elif info[0] == _TYPE_TXT:
+ rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
+ elif info[0] == _TYPE_SRV:
+ rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
+ elif info[0] == _TYPE_HINFO:
+ rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
+ elif info[0] == _TYPE_AAAA:
+ rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
+ else:
+ # Try to ignore types we don't know about
+ # this may mean the rest of the name is
+ # unable to be parsed, and may show errors
+ # so this is left for debugging. New types
+ # encountered need to be parsed properly.
+ #
+ #print "UNKNOWN TYPE = " + str(info[0])
+ #raise BadTypeInNameException
+ pass
+
+ if rec is not None:
+ self.answers.append(rec)
+
+ def isQuery(self):
+ """Returns true if this is a query"""
+ return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
+
+ def isResponse(self):
+ """Returns true if this is a response"""
+ return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
+
+ def readUTF(self, offset, len):
+ """Reads a UTF-8 string of a given length from the packet"""
+ result = self.data[offset:offset+len].decode('utf-8')
+ return result
+
+ def readName(self):
+ """Reads a domain name from the packet"""
+ result = ''
+ off = self.offset
+ next = -1
+ first = off
+
+ while 1:
+ len = ord(self.data[off])
+ off += 1
+ if len == 0:
+ break
+ t = len & 0xC0
+ if t == 0x00:
+ result = ''.join((result, self.readUTF(off, len) + '.'))
+ off += len
+ elif t == 0xC0:
+ if next < 0:
+ next = off + 1
+ off = ((len & 0x3F) << 8) | ord(self.data[off])
+ if off >= first:
+ raise "Bad domain name (circular) at " + str(off)
+ first = off
+ else:
+ raise "Bad domain name at " + str(off)
+
+ if next >= 0:
+ self.offset = next
+ else:
+ self.offset = off
+
+ return result
+
+
+class DNSOutgoing(object):
+ """Object representation of an outgoing packet"""
+
+ def __init__(self, flags, multicast = 1):
+ self.finished = 0
+ self.id = 0
+ self.multicast = multicast
+ self.flags = flags
+ self.names = {}
+ self.data = []
+ self.size = 12
+
+ self.questions = []
+ self.answers = []
+ self.authorities = []
+ self.additionals = []
+
+ def addQuestion(self, record):
+ """Adds a question"""
+ self.questions.append(record)
+
+ def addAnswer(self, inp, record):
+ """Adds an answer"""
+ if not record.suppressedBy(inp):
+ self.addAnswerAtTime(record, 0)
+
+ def addAnswerAtTime(self, record, now):
+ """Adds an answer if if does not expire by a certain time"""
+ if record is not None:
+ if now == 0 or not record.isExpired(now):
+ self.answers.append((record, now))
+
+ def addAuthorativeAnswer(self, record):
+ """Adds an authoritative answer"""
+ self.authorities.append(record)
+
+ def addAdditionalAnswer(self, record):
+ """Adds an additional answer"""
+ self.additionals.append(record)
+
+ def writeByte(self, value):
+ """Writes a single byte to the packet"""
+ format = '!c'
+ self.data.append(struct.pack(format, chr(value)))
+ self.size += 1
+
+ def insertShort(self, index, value):
+ """Inserts an unsigned short in a certain position in the packet"""
+ format = '!H'
+ self.data.insert(index, struct.pack(format, value))
+ self.size += 2
+
+ def writeShort(self, value):
+ """Writes an unsigned short to the packet"""
+ format = '!H'
+ self.data.append(struct.pack(format, value))
+ self.size += 2
+
+ def writeInt(self, value):
+ """Writes an unsigned integer to the packet"""
+ format = '!I'
+ self.data.append(struct.pack(format, int(value)))
+ self.size += 4
+
+ def writeString(self, value, length):
+ """Writes a string to the packet"""
+ format = '!' + str(length) + 's'
+ self.data.append(struct.pack(format, value))
+ self.size += length
+
+ def writeUTF(self, s):
+ """Writes a UTF-8 string of a given length to the packet"""
+ utfstr = s.encode('utf-8')
+ length = len(utfstr)
+ if length > 64:
+ raise NamePartTooLongException
+ self.writeByte(length)
+ self.writeString(utfstr, length)
+
+ def writeName(self, name):
+ """Writes a domain name to the packet"""
+
+ try:
+ # Find existing instance of this name in packet
+ #
+ index = self.names[name]
+ except KeyError:
+ # No record of this name already, so write it
+ # out as normal, recording the location of the name
+ # for future pointers to it.
+ #
+ self.names[name] = self.size
+ parts = name.split('.')
+ if parts[-1] == '':
+ parts = parts[:-1]
+ for part in parts:
+ self.writeUTF(part)
+ self.writeByte(0)
+ return
+
+ # An index was found, so write a pointer to it
+ #
+ self.writeByte((index >> 8) | 0xC0)
+ self.writeByte(index)
+
+ def writeQuestion(self, question):
+ """Writes a question to the packet"""
+ self.writeName(question.name)
+ self.writeShort(question.type)
+ self.writeShort(question.clazz)
+
+ def writeRecord(self, record, now):
+ """Writes a record (answer, authoritative answer, additional) to
+ the packet"""
+ self.writeName(record.name)
+ self.writeShort(record.type)
+ if record.unique and self.multicast:
+ self.writeShort(record.clazz | _CLASS_UNIQUE)
+ else:
+ self.writeShort(record.clazz)
+ if now == 0:
+ self.writeInt(record.ttl)
+ else:
+ self.writeInt(record.getRemainingTTL(now))
+ index = len(self.data)
+ # Adjust size for the short we will write before this record
+ #
+ self.size += 2
+ record.write(self)
+ self.size -= 2
+
+ length = len(''.join(self.data[index:]))
+ self.insertShort(index, length) # Here is the short we adjusted for
+
+ def packet(self):
+ """Returns a string containing the packet's bytes
+
+ No further parts should be added to the packet once this
+ is done."""
+ if not self.finished:
+ self.finished = 1
+ for question in self.questions:
+ self.writeQuestion(question)
+ for answer, time in self.answers:
+ self.writeRecord(answer, time)
+ for authority in self.authorities:
+ self.writeRecord(authority, 0)
+ for additional in self.additionals:
+ self.writeRecord(additional, 0)
+
+ self.insertShort(0, len(self.additionals))
+ self.insertShort(0, len(self.authorities))
+ self.insertShort(0, len(self.answers))
+ self.insertShort(0, len(self.questions))
+ self.insertShort(0, self.flags)
+ if self.multicast:
+ self.insertShort(0, 0)
+ else:
+ self.insertShort(0, self.id)
+ return ''.join(self.data)
+
+
+class DNSCache(object):
+ """A cache of DNS entries"""
+
+ def __init__(self):
+ self.cache = {}
+
+ def add(self, entry):
+ """Adds an entry"""
+ try:
+ list = self.cache[entry.key]
+ except:
+ list = self.cache[entry.key] = []
+ list.append(entry)
+
+ def remove(self, entry):
+ """Removes an entry"""
+ try:
+ list = self.cache[entry.key]
+ list.remove(entry)
+ except:
+ pass
+
+ def get(self, entry):
+ """Gets an entry by key. Will return None if there is no
+ matching entry."""
+ try:
+ list = self.cache[entry.key]
+ return list[list.index(entry)]
+ except:
+ return None
+
+ def getByDetails(self, name, type, clazz):
+ """Gets an entry by details. Will return None if there is
+ no matching entry."""
+ entry = DNSEntry(name, type, clazz)
+ return self.get(entry)
+
+ def entriesWithName(self, name):
+ """Returns a list of entries whose key matches the name."""
+ try:
+ return self.cache[name]
+ except:
+ return []
+
+ def entries(self):
+ """Returns a list of all entries"""
+ def add(x, y): return x+y
+ try:
+ return reduce(add, self.cache.values())
+ except:
+ return []
+
+
+class Engine(threading.Thread):
+ """An engine wraps read access to sockets, allowing objects that
+ need to receive data from sockets to be called back when the
+ sockets are ready.
+
+ A reader needs a handle_read() method, which is called when the socket
+ it is interested in is ready for reading.
+
+ Writers are not implemented here, because we only send short
+ packets.
+ """
+
+ def __init__(self, zeroconf):
+ threading.Thread.__init__(self)
+ self.zeroconf = zeroconf
+ self.readers = {} # maps socket to reader
+ self.timeout = 5
+ self.condition = threading.Condition()
+ self.start()
+
+ def run(self):
+ while not globals()['_GLOBAL_DONE']:
+ rs = self.getReaders()
+ if len(rs) == 0:
+ # No sockets to manage, but we wait for the timeout
+ # or addition of a socket
+ #
+ self.condition.acquire()
+ self.condition.wait(self.timeout)
+ self.condition.release()
+ else:
+ try:
+ rr, wr, er = select.select(rs, [], [], self.timeout)
+ for socket in rr:
+ try:
+ self.readers[socket].handle_read()
+ except:
+ traceback.print_exc()
+ except:
+ pass
+
+ def getReaders(self):
+ result = []
+ self.condition.acquire()
+ result = self.readers.keys()
+ self.condition.release()
+ return result
+
+ def addReader(self, reader, socket):
+ self.condition.acquire()
+ self.readers[socket] = reader
+ self.condition.notify()
+ self.condition.release()
+
+ def delReader(self, socket):
+ self.condition.acquire()
+ del(self.readers[socket])
+ self.condition.notify()
+ self.condition.release()
+
+ def notify(self):
+ self.condition.acquire()
+ self.condition.notify()
+ self.condition.release()
+
+class Listener(object):
+ """A Listener is used by this module to listen on the multicast
+ group to which DNS messages are sent, allowing the implementation
+ to cache information as it arrives.
+
+ It requires registration with an Engine object in order to have
+ the read() method called when a socket is availble for reading."""
+
+ def __init__(self, zeroconf):
+ self.zeroconf = zeroconf
+ self.zeroconf.engine.addReader(self, self.zeroconf.socket)
+
+ def handle_read(self):
+ data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
+ self.data = data
+ msg = DNSIncoming(data)
+ if msg.isQuery():
+ # Always multicast responses
+ #
+ if port == _MDNS_PORT:
+ self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
+ # If it's not a multicast query, reply via unicast
+ # and multicast
+ #
+ elif port == _DNS_PORT:
+ self.zeroconf.handleQuery(msg, addr, port)
+ self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
+ else:
+ self.zeroconf.handleResponse(msg)
+
+
+class Reaper(threading.Thread):
+ """A Reaper is used by this module to remove cache entries that
+ have expired."""
+
+ def __init__(self, zeroconf):
+ threading.Thread.__init__(self)
+ self.zeroconf = zeroconf
+ self.start()
+
+ def run(self):
+ while 1:
+ self.zeroconf.wait(10 * 1000)
+ if globals()['_GLOBAL_DONE']:
+ return
+ now = currentTimeMillis()
+ for record in self.zeroconf.cache.entries():
+ if record.isExpired(now):
+ self.zeroconf.updateRecord(now, record)
+ self.zeroconf.cache.remove(record)
+
+
+class ServiceBrowser(threading.Thread):
+ """Used to browse for a service of a specific type.
+
+ The listener object will have its addService() and
+ removeService() methods called when this browser
+ discovers changes in the services availability."""
+
+ def __init__(self, zeroconf, type, listener):
+ """Creates a browser for a specific type"""
+ threading.Thread.__init__(self)
+ self.zeroconf = zeroconf
+ self.type = type
+ self.listener = listener
+ self.services = {}
+ self.nextTime = currentTimeMillis()
+ self.delay = _BROWSER_TIME
+ self.list = []
+
+ self.done = 0
+
+ self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
+ self.start()
+
+ def updateRecord(self, zeroconf, now, record):
+ """Callback invoked by Zeroconf when new information arrives.
+
+ Updates information required by browser in the Zeroconf cache."""
+ if record.type == _TYPE_PTR and record.name == self.type:
+ expired = record.isExpired(now)
+ try:
+ oldrecord = self.services[record.alias.lower()]
+ if not expired:
+ oldrecord.resetTTL(record)
+ else:
+ del(self.services[record.alias.lower()])
+ callback = lambda x: self.listener.removeService(x, self.type, record.alias)
+ self.list.append(callback)
+ return
+ except:
+ if not expired:
+ self.services[record.alias.lower()] = record
+ callback = lambda x: self.listener.addService(x, self.type, record.alias)
+ self.list.append(callback)
+
+ expires = record.getExpirationTime(75)
+ if expires < self.nextTime:
+ self.nextTime = expires
+
+ def cancel(self):
+ self.done = 1
+ self.zeroconf.notifyAll()
+
+ def run(self):
+ while 1:
+ event = None
+ now = currentTimeMillis()
+ if len(self.list) == 0 and self.nextTime > now:
+ self.zeroconf.wait(self.nextTime - now)
+ if globals()['_GLOBAL_DONE'] or self.done:
+ return
+ now = currentTimeMillis()
+
+ if self.nextTime <= now:
+ out = DNSOutgoing(_FLAGS_QR_QUERY)
+ out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
+ for record in self.services.values():
+ if not record.isExpired(now):
+ out.addAnswerAtTime(record, now)
+ self.zeroconf.send(out)
+ self.nextTime = now + self.delay
+ self.delay = min(20 * 1000, self.delay * 2)
+
+ if len(self.list) > 0:
+ event = self.list.pop(0)
+
+ if event is not None:
+ event(self.zeroconf)
+
+
+class ServiceInfo(object):
+ """Service information"""
+
+ def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
+ """Create a service description.
+
+ type: fully qualified service type name
+ name: fully qualified service name
+ address: IP address as unsigned short, network byte order
+ port: port that the service runs on
+ weight: weight of the service
+ priority: priority of the service
+ properties: dictionary of properties (or a string holding the bytes for the text field)
+ server: fully qualified name for service host (defaults to name)"""
+
+ if not name.endswith(type):
+ raise BadTypeInNameException
+ self.type = type
+ self.name = name
+ self.address = address
+ self.port = port
+ self.weight = weight
+ self.priority = priority
+ if server:
+ self.server = server
+ else:
+ self.server = name
+ self.setProperties(properties)
+
+ def setProperties(self, properties):
+ """Sets properties and text of this info from a dictionary"""
+ if isinstance(properties, dict):
+ self.properties = properties
+ list = []
+ result = ''
+ for key in properties:
+ value = properties[key]
+ if value is None:
+ suffix = ''.encode('utf-8')
+ elif isinstance(value, str):
+ suffix = value.encode('utf-8')
+ elif isinstance(value, int):
+ if value:
+ suffix = 'true'
+ else:
+ suffix = 'false'
+ else:
+ suffix = ''.encode('utf-8')
+ list.append('='.join((key, suffix)))
+ for item in list:
+ result = ''.join((result, struct.pack('!c', chr(len(item))), item))
+ self.text = result
+ else:
+ self.text = properties
+
+ def setText(self, text):
+ """Sets properties and text given a text field"""
+ self.text = text
+ try:
+ result = {}
+ end = len(text)
+ index = 0
+ strs = []
+ while index < end:
+ length = ord(text[index])
+ index += 1
+ strs.append(text[index:index+length])
+ index += length
+
+ for s in strs:
+ eindex = s.find('=')
+ if eindex == -1:
+ # No equals sign at all
+ key = s
+ value = 0
+ else:
+ key = s[:eindex]
+ value = s[eindex+1:]
+ if value == 'true':
+ value = 1
+ elif value == 'false' or not value:
+ value = 0
+
+ # Only update non-existent properties
+ if key and result.get(key) == None:
+ result[key] = value
+
+ self.properties = result
+ except:
+ traceback.print_exc()
+ self.properties = None
+
+ def getType(self):
+ """Type accessor"""
+ return self.type
+
+ def getName(self):
+ """Name accessor"""
+ if self.type is not None and self.name.endswith("." + self.type):
+ return self.name[:len(self.name) - len(self.type) - 1]
+ return self.name
+
+ def getAddress(self):
+ """Address accessor"""
+ return self.address
+
+ def getPort(self):
+ """Port accessor"""
+ return self.port
+
+ def getPriority(self):
+ """Pirority accessor"""
+ return self.priority
+
+ def getWeight(self):
+ """Weight accessor"""
+ return self.weight
+
+ def getProperties(self):
+ """Properties accessor"""
+ return self.properties
+
+ def getText(self):
+ """Text accessor"""
+ return self.text
+
+ def getServer(self):
+ """Server accessor"""
+ return self.server
+
+ def updateRecord(self, zeroconf, now, record):
+ """Updates service information from a DNS record"""
+ if record is not None and not record.isExpired(now):
+ if record.type == _TYPE_A:
+ if record.name == self.name:
+ self.address = record.address
+ elif record.type == _TYPE_SRV:
+ if record.name == self.name:
+ self.server = record.server
+ self.port = record.port
+ self.weight = record.weight
+ self.priority = record.priority
+ self.address = None
+ self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
+ elif record.type == _TYPE_TXT:
+ if record.name == self.name:
+ self.setText(record.text)
+
+ def request(self, zeroconf, timeout):
+ """Returns true if the service could be discovered on the
+ network, and updates this object with details discovered.
+ """
+ now = currentTimeMillis()
+ delay = _LISTENER_TIME
+ next = now + delay
+ last = now + timeout
+ result = 0
+ try:
+ zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
+ while self.server is None or self.address is None or self.text is None:
+ if last <= now:
+ return 0
+ if next <= now:
+ out = DNSOutgoing(_FLAGS_QR_QUERY)
+ out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
+ out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
+ out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
+ out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
+ if self.server is not None:
+ out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
+ out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
+ zeroconf.send(out)
+ next = now + delay
+ delay = delay * 2
+
+ zeroconf.wait(min(next, last) - now)
+ now = currentTimeMillis()
+ result = 1
+ finally:
+ zeroconf.removeListener(self)
+
+ return result
+
+ def __eq__(self, other):
+ """Tests equality of service name"""
+ if isinstance(other, ServiceInfo):
+ return other.name == self.name
+ return 0
+
+ def __ne__(self, other):
+ """Non-equality test"""
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ """String representation"""
+ result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
+ if self.text is None:
+ result += "None"
+ else:
+ if len(self.text) < 20:
+ result += self.text
+ else:
+ result += self.text[:17] + "..."
+ result += "]"
+ return result
+
+
+class Zeroconf(object):
+ """Implementation of Zeroconf Multicast DNS Service Discovery
+
+ Supports registration, unregistration, queries and browsing.
+ """
+ def __init__(self, bindaddress=None):
+ """Creates an instance of the Zeroconf class, establishing
+ multicast communications, listening and reaping threads."""
+ globals()['_GLOBAL_DONE'] = 0
+ if bindaddress is None:
+ self.intf = socket.gethostbyname(socket.gethostname())
+ else:
+ self.intf = bindaddress
+ self.group = ('', _MDNS_PORT)
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ except:
+ # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
+ # multicast UDP sockets (p 731, "TCP/IP Illustrated,
+ # Volume 2"), but some BSD-derived systems require
+ # SO_REUSEPORT to be specified explicity. Also, not all
+ # versions of Python have SO_REUSEPORT available. So
+ # if you're on a BSD-based system, and haven't upgraded
+ # to Python 2.3 yet, you may find this library doesn't
+ # work as expected.
+ #
+ pass
+ self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
+ self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
+ try:
+ self.socket.bind(self.group)
+ except:
+ # Some versions of linux raise an exception even though
+ # the SO_REUSE* options have been set, so ignore it
+ #
+ pass
+ self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
+ self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
+
+ self.listeners = []
+ self.browsers = []
+ self.services = {}
+
+ self.cache = DNSCache()
+
+ self.condition = threading.Condition()
+
+ self.engine = Engine(self)
+ self.listener = Listener(self)
+ self.reaper = Reaper(self)
+
+ def isLoopback(self):
+ return self.intf.startswith("127.0.0.1")
+
+ def isLinklocal(self):
+ return self.intf.startswith("169.254.")
+
+ def wait(self, timeout):
+ """Calling thread waits for a given number of milliseconds or
+ until notified."""
+ self.condition.acquire()
+ self.condition.wait(timeout/1000)
+ self.condition.release()
+
+ def notifyAll(self):
+ """Notifies all waiting threads"""
+ self.condition.acquire()
+ self.condition.notifyAll()
+ self.condition.release()
+
+ def getServiceInfo(self, type, name, timeout=3000):
+ """Returns network's service information for a particular
+ name and type, or None if no service matches by the timeout,
+ which defaults to 3 seconds."""
+ info = ServiceInfo(type, name)
+ if info.request(self, timeout):
+ return info
+ return None
+
+ def addServiceListener(self, type, listener):
+ """Adds a listener for a particular service type. This object
+ will then have its updateRecord method called when information
+ arrives for that type."""
+ self.removeServiceListener(listener)
+ self.browsers.append(ServiceBrowser(self, type, listener))
+
+ def removeServiceListener(self, listener):
+ """Removes a listener from the set that is currently listening."""
+ for browser in self.browsers:
+ if browser.listener == listener:
+ browser.cancel()
+ del(browser)
+
+ def registerService(self, info, ttl=_DNS_TTL):
+ """Registers service information to the network with a default TTL
+ of 60 seconds. Zeroconf will then respond to requests for
+ information for that service. The name of the service may be
+ changed if needed to make it unique on the network."""
+ self.checkService(info)
+ self.services[info.name.lower()] = info
+ now = currentTimeMillis()
+ nextTime = now
+ i = 0
+ while i < 3:
+ if now < nextTime:
+ self.wait(nextTime - now)
+ now = currentTimeMillis()
+ continue
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
+ out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
+ out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
+ out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
+ if info.address:
+ out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
+ self.send(out)
+ i += 1
+ nextTime += _REGISTER_TIME
+
+ def unregisterService(self, info):
+ """Unregister a service."""
+ try:
+ del(self.services[info.name.lower()])
+ except:
+ pass
+ now = currentTimeMillis()
+ nextTime = now
+ i = 0
+ while i < 3:
+ if now < nextTime:
+ self.wait(nextTime - now)
+ now = currentTimeMillis()
+ continue
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
+ out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
+ out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
+ out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
+ if info.address:
+ out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
+ self.send(out)
+ i += 1
+ nextTime += _UNREGISTER_TIME
+
+ def unregisterAllServices(self):
+ """Unregister all registered services."""
+ if len(self.services) > 0:
+ now = currentTimeMillis()
+ nextTime = now
+ i = 0
+ while i < 3:
+ if now < nextTime:
+ self.wait(nextTime - now)
+ now = currentTimeMillis()
+ continue
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
+ for info in self.services.values():
+ out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
+ out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
+ out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
+ if info.address:
+ out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
+ self.send(out)
+ i += 1
+ nextTime += _UNREGISTER_TIME
+
+ def checkService(self, info):
+ """Checks the network for a unique service name, modifying the
+ ServiceInfo passed in if it is not unique."""
+ now = currentTimeMillis()
+ nextTime = now
+ i = 0
+ while i < 3:
+ for record in self.cache.entriesWithName(info.type):
+ if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
+ if (info.name.find('.') < 0):
+ info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
+ self.checkService(info)
+ return
+ raise NonUniqueNameException
+ if now < nextTime:
+ self.wait(nextTime - now)
+ now = currentTimeMillis()
+ continue
+ out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
+ self.debug = out
+ out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
+ out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
+ self.send(out)
+ i += 1
+ nextTime += _CHECK_TIME
+
+ def addListener(self, listener, question):
+ """Adds a listener for a given question. The listener will have
+ its updateRecord method called when information is available to
+ answer the question."""
+ now = currentTimeMillis()
+ self.listeners.append(listener)
+ if question is not None:
+ for record in self.cache.entriesWithName(question.name):
+ if question.answeredBy(record) and not record.isExpired(now):
+ listener.updateRecord(self, now, record)
+ self.notifyAll()
+
+ def removeListener(self, listener):
+ """Removes a listener."""
+ try:
+ self.listeners.remove(listener)
+ self.notifyAll()
+ except:
+ pass
+
+ def updateRecord(self, now, rec):
+ """Used to notify listeners of new information that has updated
+ a record."""
+ for listener in self.listeners:
+ listener.updateRecord(self, now, rec)
+ self.notifyAll()
+
+ def handleResponse(self, msg):
+ """Deal with incoming response packets. All answers
+ are held in the cache, and listeners are notified."""
+ now = currentTimeMillis()
+ for record in msg.answers:
+ expired = record.isExpired(now)
+ if record in self.cache.entries():
+ if expired:
+ self.cache.remove(record)
+ else:
+ entry = self.cache.get(record)
+ if entry is not None:
+ entry.resetTTL(record)
+ record = entry
+ else:
+ self.cache.add(record)
+
+ self.updateRecord(now, record)
+
+ def handleQuery(self, msg, addr, port):
+ """Deal with incoming query packets. Provides a response if
+ possible."""
+ out = None
+
+ # Support unicast client responses
+ #
+ if port != _MDNS_PORT:
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
+ for question in msg.questions:
+ out.addQuestion(question)
+
+ for question in msg.questions:
+ if question.type == _TYPE_PTR:
+ for service in self.services.values():
+ if question.name == service.type:
+ if out is None:
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
+ out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
+ else:
+ try:
+ if out is None:
+ out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
+
+ # Answer A record queries for any service addresses we know
+ if question.type == _TYPE_A or question.type == _TYPE_ANY:
+ for service in self.services.values():
+ if service.server == question.name.lower():
+ out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
+
+ service = self.services.get(question.name.lower(), None)
+ if not service: continue
+
+ if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
+ out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
+ if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
+ out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
+ if question.type == _TYPE_SRV:
+ out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
+ except:
+ traceback.print_exc()
+
+ if out is not None and out.answers:
+ out.id = msg.id
+ self.send(out, addr, port)
+
+ def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
+ """Sends an outgoing packet."""
+ # This is a quick test to see if we can parse the packets we generate
+ #temp = DNSIncoming(out.packet())
+ try:
+ bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
+ except:
+ # Ignore this, it may be a temporary loss of network connection
+ pass
+
+ def close(self):
+ """Ends the background threads, and prevent this instance from
+ servicing further queries."""
+ if globals()['_GLOBAL_DONE'] == 0:
+ globals()['_GLOBAL_DONE'] = 1
+ self.notifyAll()
+ self.engine.notify()
+ self.unregisterAllServices()
+ self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
+ self.socket.close()
+
+# Test a few module features, including service registration, service
+# query (for Zoe), and service unregistration.
+
+if __name__ == '__main__':
+ print "Multicast DNS Service Discovery for Python, version", __version__
+ r = Zeroconf()
+ print "1. Testing registration of a service..."
+ desc = {'version':'0.10','a':'test value', 'b':'another value'}
+ info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
+ print " Registering service..."
+ r.registerService(info)
+ print " Registration done."
+ print "2. Testing query of service information..."
+ print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
+ print " Query done."
+ print "3. Testing query of own service..."
+ print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
+ print " Query done."
+ print "4. Testing unregister of service information..."
+ r.unregisterService(info)
+ print " Unregister done."
+ r.close()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/PYRO/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#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 Pyro.core as pyro
+from Pyro.errors import PyroError
+import traceback
+from time import sleep
+
+def PYRO_connector_factory(uri, pluginsroot):
+ """
+ This returns the connector to Pyro style PLCobject
+ """
+ pluginsroot.logger.write("Connecting to URI : %s\n"%uri)
+
+ servicetype, location = uri.split("://")
+
+ # Try to get the proxy object
+ try :
+ RemotePLCObjectProxy = pyro.getAttrProxyForURI("PYROLOC://"+location+"/PLCObject")
+ except Exception, msg:
+ pluginsroot.logger.write_error("Wrong URI, please check it !\n")
+ pluginsroot.logger.write_error(traceback.format_exc())
+ return None
+
+ def PyroCatcher(func, default=None):
+ """
+ A function that catch a pyro exceptions, write error to logger
+ and return defaul value when it happen
+ """
+ def cather_func(*args,**kwargs):
+ try:
+ return func(*args,**kwargs)
+ except PyroError,e:
+ #pluginsroot.logger.write_error(traceback.format_exc())
+ pluginsroot.logger.write_error(str(e))
+ pluginsroot._Disconnect()
+ return default
+ return cather_func
+
+ # Check connection is effective.
+ # lambda is for getattr of GetPLCstatus to happen inside catcher
+ if PyroCatcher(lambda:RemotePLCObjectProxy.GetPLCstatus())() == None:
+ pluginsroot.logger.write_error("Cannot get PLC status - connection failed.\n")
+ return None
+
+ class PyroProxyProxy:
+ """
+ A proxy proxy class to handle Beremiz Pyro interface specific behavior.
+ And to put pyro exception catcher in between caller and pyro proxy
+ """
+ def GetPyroProxy(self):
+ """
+ This func returns the real Pyro Proxy.
+ Use this if you musn't keep reference to it.
+ """
+ return RemotePLCObjectProxy
+
+ def __getattr__(self, attrName):
+ if not self.__dict__.has_key(attrName):
+ if attrName=="StartPLC":
+ def _StartPLC():
+ """
+ pluginsroot._connector.GetPyroProxy() is used
+ rather than RemotePLCObjectProxy because
+ object is recreated meanwhile,
+ so we must not keep ref to it here
+ """
+ if pluginsroot._connector.GetPyroProxy().GetPLCstatus() == "Dirty":
+ """
+ Some bad libs with static symbols may polute PLC
+ ask runtime to suicide and come back again
+ """
+ pluginsroot.logger.write("Force runtime reload\n")
+ pluginsroot._connector.GetPyroProxy().ForceReload()
+ pluginsroot._Disconnect()
+ # let remote PLC time to resurect.(freeze app)
+ sleep(0.5)
+ pluginsroot._Connect()
+ return pluginsroot._connector.GetPyroProxy().StartPLC()
+ member = PyroCatcher(_StartPLC, False)
+ else:
+ def my_local_func(*args,**kwargs):
+ return RemotePLCObjectProxy.__getattr__(attrName)(*args,**kwargs)
+ member = PyroCatcher(my_local_func, None)
+ self.__dict__[attrName] = member
+ return self.__dict__[attrName]
+ return PyroProxyProxy()
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/USB/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from connector_USB import *
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialisation
+
+from os import listdir, path
+
+_base_path = path.split(__file__)[0]
+
+connector_types = [name for name in listdir(_base_path) if path.isdir(path.join(_base_path, name)) and name.upper() != "CVS" and not name.startswith("__")]
+
+def ConnectorFactory(uri, pluginsroot):
+ """
+ Return a connector corresponding to the URI
+ or None if cannot connect to URI
+ """
+ servicetype = uri.split("://")[0]
+ if servicetype in connector_types:
+ # import module according to uri type
+ connectormodule = getattr(__import__("connectors."+servicetype), servicetype)
+ factoryname = servicetype + "_connector_factory"
+ return getattr(connectormodule, factoryname)(uri, pluginsroot)
+ else :
+ return None
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/discovery.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#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 wx
+from Zeroconf import *
+import socket
+import wx.lib.mixins.listctrl as listmix
+
+class TestListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
+ def __init__(self, parent, ID, pos=wx.DefaultPosition,
+ size=wx.DefaultSize, style=0):
+ wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
+ listmix.ListCtrlAutoWidthMixin.__init__(self)
+
+class DiscoveryDialog(wx.Dialog, listmix.ColumnSorterMixin):
+ def __init__(self, parent, id=-1, title='Service Discovery'):
+ self.my_result=None
+ self.itemDataMap = {}
+ wx.Dialog.__init__(self, parent, id, title, size=(600,600), style=wx.DEFAULT_DIALOG_STYLE)
+
+ self.list = TestListCtrl(self, -1,
+ pos=(50,20),
+ size=(500,300),
+ style=wx.LC_REPORT
+ | wx.LC_EDIT_LABELS
+ | wx.LC_SORT_ASCENDING
+ )
+ self.PopulateList()
+
+ self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list)
+ self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.list)
+ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self.list)
+ self.Bind(wx.EVT_LIST_DELETE_ITEM, self.OnItemDelete, self.list)
+ self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)
+ self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
+
+ b = wx.Button(self,20, "Connect", (175, 500))
+ self.Bind(wx.EVT_BUTTON, self.OnConnect, b)
+ b.SetSize(b.GetBestSize())
+
+ b = wx.Button(self, 40, "Cancel", (350, 500))
+ self.Bind(wx.EVT_BUTTON, self.OnClose, b)
+ b.SetSize(b.GetBestSize())
+
+ #type = "_http._tcp.local."
+ type = "_PYRO._tcp.local."
+ self.r = Zeroconf()
+ browser = ServiceBrowser(self.r, type, self)
+
+ listmix.ColumnSorterMixin.__init__(self, 4)
+ # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+ def GetListCtrl(self):
+ return self.list
+
+ def PopulateList(self):
+ self.list.InsertColumn(0, 'NAME')
+ self.list.InsertColumn(1, 'TYPE')
+ self.list.InsertColumn(2, 'IP')
+ self.list.InsertColumn(3, 'PORT')
+ self.list.SetColumnWidth(0, 150)
+ self.list.SetColumnWidth(1, 150)
+ self.list.SetColumnWidth(2, 150)
+ self.list.SetColumnWidth(3, 150)
+
+ def getColumnText(self, index, col):
+ item = self.list.GetItem(index, col)
+ return item.GetText()
+
+ def OnItemSelected(self, event):
+ self.currentItem = event.m_itemIndex
+ print "OnItemSelected: %s, %s, %s, %s\n"%(self.currentItem,
+ self.list.GetItemText(self.currentItem),
+ self.getColumnText(self.currentItem, 1),
+ self.getColumnText(self.currentItem, 2))
+ event.Skip()
+
+
+ def OnItemDeselected(self, evt):
+ item = evt.GetItem()
+ print "OnItemDeselected: %d" % evt.m_itemIndex
+
+ def OnItemActivated(self, event):
+ self.currentItem = event.m_itemIndex
+ print "OnItemActivated: %s\nTopItem: %s" %(self.list.GetItemText(self.currentItem), self.list.GetTopItem())
+
+ def OnItemDelete(self, event):
+ print "OnItemDelete\n"
+
+ def OnColClick(self, event):
+ print "OnColClick: %d\n" % event.GetColumn()
+ event.Skip()
+
+ def OnColRightClick(self, event):
+ item = self.list.GetColumn(event.GetColumn())
+ print "OnColRightClick: %d %s\n" %(event.GetColumn(), (item.GetText(), item.GetAlign(),
+ item.GetWidth(), item.GetImage()))
+ def OnDoubleClick(self, event):
+ connect_type = self.getColumnText(self.currentItem, 1)
+ connect_address = self.getColumnText(self.currentItem, 2)
+ connect_port = self.getColumnText(self.currentItem, 3)
+
+ uri = self.CreateURI(connect_type, connect_address, connect_port)
+ self.my_result=uri
+ event.Skip()
+
+ def GetResult(self):
+ return self.my_result
+
+ def OnClick(self, event):
+ print "Click! (%d)\n" %event.GetId()
+ index = self.list.GetFocusedItem()
+ self.list.DeleteItem(index)
+ print "Service", name, "removed"
+
+ def removeService(self, zeroconf, type, name):
+ index = self.list.GetFocusedItem()
+
+ def addService(self, zeroconf, type, name):
+ info = self.r.getServiceInfo(type, name)
+ typename = type.split(".")[0][1:]
+ num_items = self.list.GetItemCount()
+ self.itemDataMap[num_items] = (name, "%s"%type, "%s"%str(socket.inet_ntoa(info.getAddress())), "%s"%info.getPort())
+ self.list.InsertStringItem(num_items, name.split(".")[0])
+ self.list.SetStringItem(num_items, 1, "%s"%typename)
+ self.list.SetStringItem(num_items, 2, "%s"%str(socket.inet_ntoa(info.getAddress())))
+ self.list.SetStringItem(num_items, 3, "%s"%info.getPort())
+
+ def CreateURI(self, connect_type, connect_address, connect_port):
+ uri = "%s://%s:%s"%(connect_type, connect_address, connect_port)
+ print uri
+ return uri
+
+ def OnAdd(self, event):
+ num_items = self.list.GetItemCount()
+ self.list.InsertStringItem(num_items, self.tc1.GetValue())
+ self.list.SetStringItem(num_items, 1, self.tc2.GetValue())
+
+ def OnRemove(self, event):
+ index = self.list.GetFocusedItem()
+ self.list.DeleteItem(index)
+
+ def OnConnect(self, event):
+ index = self.list.GetFocusedItem()
+ print self.list.GetItemData(index)
+
+ def OnClose(self, event):
+ self.Close()
+
+ def OnClear(self, event):
+ self.list.DeleteAllItems()
Binary file images/Connect.png has changed
Binary file images/Debug.png has changed
Binary file images/Disconnect.png has changed
Binary file images/Transfer.png has changed
--- a/images/icons.svg Tue Aug 12 16:27:07 2008 +0200
+++ b/images/icons.svg Wed Aug 20 00:11:40 2008 +0200
@@ -30,7 +30,7 @@
</metadata>
<sodipodi:namedview
inkscape:window-height="994"
- inkscape:window-width="1625"
+ inkscape:window-width="1623"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
@@ -40,12 +40,12 @@
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
- showgrid="true"
+ showgrid="false"
inkscape:zoom="1"
- inkscape:cx="440.43263"
- inkscape:cy="865.35999"
- inkscape:window-x="47"
- inkscape:window-y="25"
+ inkscape:cx="514.46278"
+ inkscape:cy="430.8377"
+ inkscape:window-x="52"
+ inkscape:window-y="51"
inkscape:current-layer="svg2"
showguides="true"
inkscape:guide-bbox="true">
@@ -56,6 +56,30 @@
<defs
id="defs4">
<linearGradient
+ inkscape:collect="always"
+ id="linearGradient17546">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop17548" />
+ <stop
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1"
+ id="stop17550" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient17526">
+ <stop
+ style="stop-color:#469837;stop-opacity:1;"
+ offset="0"
+ id="stop17528" />
+ <stop
+ style="stop-color:#469837;stop-opacity:0;"
+ offset="1"
+ id="stop17530" />
+ </linearGradient>
+ <linearGradient
id="linearGradient2345">
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
@@ -82531,10 +82555,116 @@
y1="221.98289"
x2="46.488174"
y2="259.94464" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16478"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16480"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16596"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16598"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16715"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient34137"
+ id="linearGradient16717"
+ gradientUnits="userSpaceOnUse"
+ x1="-77.844841"
+ y1="5.1423945"
+ x2="-77.844841"
+ y2="14.276564" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient17526"
+ id="linearGradient17532"
+ x1="335"
+ y1="137.36218"
+ x2="335"
+ y2="144.96741"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ gradientTransform="translate(60,0)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient17526"
+ id="linearGradient17534"
+ x1="335"
+ y1="137.36218"
+ x2="335"
+ y2="144.96741"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ gradientTransform="translate(60,0)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient17526"
+ id="linearGradient17536"
+ x1="335"
+ y1="137.36218"
+ x2="335"
+ y2="144.96741"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ gradientTransform="translate(60,0)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient17526"
+ id="linearGradient17540"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="335"
+ y1="137.36218"
+ x2="335"
+ y2="144.96741" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient17546"
+ id="linearGradient17552"
+ x1="329.43661"
+ y1="145.7263"
+ x2="336.79922"
+ y2="141.47549"
+ gradientUnits="userSpaceOnUse" />
</defs>
<g
id="g19063"
- transform="matrix(0.9968636,0,0,0.9968648,-6.725278,-12.08626)">
+ transform="matrix(0.9968636,0,0,0.9968648,-6.725278,-192.08626)">
<rect
width="24.075478"
height="24.075478"
@@ -82637,7 +82767,7 @@
</g>
</g>
<g
- transform="matrix(1.2234367,0,0,1.2234367,1268.4713,-201.46094)"
+ transform="matrix(1.2234367,0,0,1.2234367,1268.4713,-281.46094)"
id="g17987">
<g
style="display:inline"
@@ -82695,21 +82825,21 @@
style="font-size:12.76095104px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
xml:space="preserve"
id="text2713"
- y="241.52583"
+ y="181.52583"
x="33.295933"><tspan
- y="241.52583"
+ y="181.52583"
x="33.295933"
id="tspan16193"
sodipodi:role="line">%% Build Clean editPLC HMIEditor ImportDEF ImportSVG NetworkEdit Run ShowIECcode Stop Unknown %%</tspan></text>
<rect
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="Unknown"
- y="271.36218"
+ y="191.36218"
x="660"
height="24"
width="24" />
<g
- transform="translate(1181,-139.4776)"
+ transform="translate(1181,-219.4776)"
id="g16213">
<rect
width="24"
@@ -82773,7 +82903,7 @@
</g>
</g>
<g
- transform="translate(1240.7988,-169.49646)"
+ transform="translate(1240.7988,-249.49646)"
id="g16489">
<g
id="g16229">
@@ -82869,7 +82999,7 @@
d="M -1045.6271,459.85458 L -1044.4626,460.28154 L -1044.4534,460.25632 L -1045.6179,459.82936 L -1045.6271,459.85458 z M -1045.483,459.46147 L -1044.8985,459.67576 L -1044.8893,459.65054 L -1045.4737,459.43625 L -1045.483,459.46147 z M -1045.3394,459.06983 L -1044.7549,459.28413 L -1044.7451,459.25743 L -1045.3296,459.04313 L -1045.3394,459.06983 z M -1045.1953,458.67671 L -1044.6108,458.89101 L -1044.6015,458.86579 L -1045.186,458.6515 L -1045.1953,458.67671 z M -1045.0511,458.2836 L -1044.4666,458.49789 L -1044.4574,458.47267 L -1045.0419,458.25838 L -1045.0511,458.2836 z M -1044.907,457.89048 L -1043.7425,458.31744 L -1043.7332,458.29222 L -1044.8977,457.86526 L -1044.907,457.89048 z M -1044.7629,457.49736 L -1044.1784,457.71166 L -1044.1691,457.68644 L -1044.7536,457.47214 L -1044.7629,457.49736 z M -1044.6193,457.10573 L -1044.0348,457.32003 L -1044.025,457.29332 L -1044.6095,457.07903 L -1044.6193,457.10573 z M -1044.4751,456.71261 L -1043.8907,456.92691 L -1043.8814,456.90169 L -1044.4659,456.68739 L -1044.4751,456.71261 z M -1044.331,456.3195 L -1043.7465,456.53379 L -1043.7373,456.50857 L -1044.3218,456.29428 L -1044.331,456.3195 z M -1044.1869,455.92638 L -1043.0224,456.35334 L -1043.0131,456.32812 L -1044.1776,455.90116 L -1044.1869,455.92638 z M -1044.0427,455.53326 L -1043.4583,455.74756 L -1043.449,455.72234 L -1044.0335,455.50804 L -1044.0427,455.53326 z M -1043.8986,455.14014 L -1043.3141,455.35444 L -1043.3049,455.32922 L -1043.8894,455.11493 L -1043.8986,455.14014 z M -1043.755,454.74851 L -1043.1705,454.96281 L -1043.1607,454.9361 L -1043.7452,454.72181 L -1043.755,454.74851 z M -1043.6109,454.35539 L -1043.0264,454.56969 L -1043.0172,454.54447 L -1043.6016,454.33017 L -1043.6109,454.35539 z M -1043.4667,453.96228 L -1042.3022,454.38924 L -1042.293,454.36402 L -1043.4575,453.93706 L -1043.4667,453.96228 z M -1043.3226,453.56916 L -1042.7381,453.78346 L -1042.7289,453.75824 L -1043.3134,453.54394 L -1043.3226,453.56916 z M -1043.1785,453.17604 L -1042.594,453.39034 L -1042.5848,453.36512 L -1043.1692,453.15082 L -1043.1785,453.17604 z M -1043.0349,452.78441 L -1042.4504,452.9987 L -1042.4406,452.972 L -1043.0251,452.75771 L -1043.0349,452.78441 z M -1042.8908,452.39129 L -1042.3063,452.60559 L -1042.297,452.58037 L -1042.8815,452.36607 L -1042.8908,452.39129 z M -1042.7466,451.99817 L -1041.5821,452.42513 L -1041.5729,452.39992 L -1042.7374,451.97295 L -1042.7466,451.99817 z M -1042.6025,451.60506 L -1042.018,451.81935 L -1042.0088,451.79413 L -1042.5932,451.57984 L -1042.6025,451.60506 z M -1042.4584,451.21194 L -1041.8739,451.42624 L -1041.8646,451.40102 L -1042.4491,451.18672 L -1042.4584,451.21194 z M -1042.3148,450.82031 L -1041.7303,451.0346 L -1041.7205,451.0079 L -1042.305,450.7936 L -1042.3148,450.82031 z M -1042.1706,450.42719 L -1041.5862,450.64148 L -1041.5769,450.61627 L -1042.1614,450.40197 L -1042.1706,450.42719 z M -1042.0265,450.03407 L -1040.862,450.46103 L -1040.8527,450.43581 L -1042.0173,450.00885 L -1042.0265,450.03407 z M -1041.8824,449.64095 L -1041.2979,449.85525 L -1041.2886,449.83003 L -1041.8731,449.61573 L -1041.8824,449.64095 z M -1041.7382,449.24784 L -1041.1538,449.46213 L -1041.1445,449.43692 L -1041.729,449.22262 L -1041.7382,449.24784 z M -1041.5946,448.8562 L -1041.0102,449.0705 L -1041.0004,449.0438 L -1041.5849,448.8295 L -1041.5946,448.8562 z M -1041.4505,448.46309 L -1040.866,448.67738 L -1040.8568,448.65216 L -1041.4413,448.43787 L -1041.4505,448.46309 z M -1041.3064,448.06997 L -1040.1419,448.49693 L -1040.1326,448.47171 L -1041.2971,448.04475 L -1041.3064,448.06997 z" />
</g>
<g
- transform="translate(1541.0897,-320.03854)"
+ transform="translate(1541.0897,-400.03854)"
id="g16620">
<g
id="g16343">
@@ -82887,7 +83017,7 @@
d="M -1043.3148,591.51852 C -1042.1671,591.51852 -1041.2381,592.51591 -1041.2381,593.74961 C -1041.2381,594.98052 -1042.1671,595.97828 -1043.3148,595.97828 C -1044.4596,595.97828 -1045.3867,594.98052 -1045.3867,593.74961 C -1045.3867,592.51591 -1044.4596,591.51852 -1043.3148,591.51852 z M -1048.4282,594.44426 C -1047.4919,594.46171 -1046.5801,594.73882 -1046.0331,595.02798 C -1045.4412,595.33917 -1045.4725,595.99227 -1045.1141,596.27013 C -1044.75,596.52298 -1044.2166,596.30364 -1043.8888,596.59817 C -1043.5554,596.88714 -1043.3383,597.38735 -1043.1411,598.02366 C -1042.941,598.65718 -1043.2194,599.51581 -1042.6997,600.36329 C -1042.1579,601.20798 -1041.1693,601.98435 -1039.9838,603.03094 C -1039.7383,603.24774 -1039.4784,603.67038 -1039.5738,603.94508 C -1039.7674,604.50302 -1040.1089,604.66757 -1040.459,604.7121 C -1040.8091,604.74545 -1041.1452,604.49515 -1041.6481,604.16458 C -1042.14,603.81725 -1042.8525,603.29754 -1043.4137,602.70291 C -1043.9722,602.10273 -1044.4846,601.4192 -1044.979,600.61895 L -1046.3032,602.37247 C -1045.4751,603.03656 -1044.7728,603.68364 -1044.1614,604.34789 C -1043.5363,605.00904 -1042.891,605.58171 -1042.6659,606.32089 C -1042.4436,607.04889 -1042.5963,607.49919 -1042.8686,608.73288 C -1043.1436,609.97493 -1043.8931,612.61785 -1044.2989,613.70159 C -1044.6934,614.763 -1044.8709,614.98822 -1045.2516,615.12708 C -1045.6323,615.25775 -1046.3979,615.17954 -1046.5758,614.50719 C -1046.7396,613.82374 -1046.4196,612.25651 -1046.2695,611.07011 C -1046.1167,609.87237 -1045.929,608.683 -1045.693,607.41594 L -1048.4475,605.33439 C -1050.0729,607.62951 -1051.3606,609.44427 -1052.3887,610.85301 C -1053.4056,612.24789 -1053.9336,613.01256 -1054.5281,613.70159 C -1055.1089,614.38782 -1055.4351,614.80205 -1055.8909,614.94375 C -1056.3466,615.06872 -1057.1123,614.96846 -1057.2151,614.47102 C -1057.2957,613.96539 -1057.1611,613.44582 -1056.3998,611.94807 C -1055.6162,610.42825 -1054.0144,607.99671 -1052.6251,605.47911 C -1051.2276,602.95351 -1049.7723,600.14902 -1048.1412,596.92861 C -1048.9386,596.61185 -1049.7389,596.5703 -1050.5532,596.78147 C -1051.3756,596.99266 -1052.3594,597.95412 -1053.0013,598.20696 C -1053.6348,598.44592 -1054.0888,598.45432 -1054.2917,598.24314 C -1054.4806,598.02363 -1054.5035,597.44543 -1054.1229,596.92861 C -1053.7201,596.40067 -1052.9063,595.5533 -1051.9811,595.1365 C -1051.0501,594.71693 -1049.6111,594.46093 -1048.6163,594.44426 C -1048.5538,594.44305 -1048.4906,594.44309 -1048.4282,594.44426 z" />
</g>
<g
- transform="translate(1480.1847,-289.94418)"
+ transform="translate(1480.1847,-369.94418)"
id="g16552">
<g
id="g16340">
@@ -83140,7 +83270,7 @@
</g>
<g
id="g16346"
- transform="translate(1600.9892,-350.1329)">
+ transform="translate(1600.9892,-430.1329)">
<rect
width="24"
height="24"
@@ -83150,7 +83280,7 @@
style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
<flowRoot
- transform="matrix(1.6473499,0,0,1.6473499,680.92343,263.57576)"
+ transform="matrix(1.6473499,0,0,1.6473499,680.92343,183.57576)"
id="flowRoot29856"
xml:space="preserve"
style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient19976);fill-opacity:1;stroke:#547c1b;stroke-width:0.1061436;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Andale Mono"><flowRegion
@@ -83164,7 +83294,7 @@
style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient34167);fill-opacity:1;stroke:#547c1b;stroke-width:0.1061436;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Andale Mono" /></flowRegion><flowPara
id="flowPara29862"
style="fill:url(#linearGradient19974);fill-opacity:1;stroke:#547c1b;stroke-width:0.1061436;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">ST</flowPara></flowRoot> <g
- transform="matrix(7.1599763e-2,0,0,7.1599763e-2,543.18029,275.95335)"
+ transform="matrix(7.1599763e-2,0,0,7.1599763e-2,543.18029,195.95335)"
id="g2248">
<path
d="M 144.80549,88.557517 C 144.80549,127.69251 113.04391,159.45419 73.909089,159.45419 C 34.773922,159.45419 3.0123414,127.69251 3.0123414,88.557517 C 3.0123414,49.42256 34.773922,17.660874 73.909089,17.660874 C 113.04391,17.660874 144.80549,49.42256 144.80549,88.557517 z"
@@ -83237,7 +83367,7 @@
style="opacity:0.84418604;fill:#6d9d37;fill-opacity:1;stroke:none" />
</g>
<g
- transform="translate(1660.8886,-380.22727)"
+ transform="translate(1660.8886,-460.22727)"
id="g16694">
<g
id="g16349">
@@ -83332,7 +83462,7 @@
id="g1161"
transform="matrix(0.5724346,-0.3079575,0.3079575,0.5724346,131.42904,887.47867)" />
<g
- transform="translate(1360.788,-229.831)"
+ transform="translate(1360.788,-309.831)"
id="g16313">
<rect
width="24"
@@ -83408,7 +83538,7 @@
</g>
</g>
<g
- transform="translate(1420.788,-259.84989)"
+ transform="translate(1420.788,-339.84989)"
id="g16328">
<rect
width="24"
@@ -83464,7 +83594,7 @@
</g>
</g>
<g
- transform="translate(1121,-109.38324)"
+ transform="translate(1121,-189.38324)"
id="g16199">
<rect
width="24"
@@ -83522,7 +83652,7 @@
<rect
style="fill:none;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="HMIEditor"
- y="271.36218"
+ y="191.36218"
x="240"
height="24"
width="24" />
@@ -83531,9 +83661,9 @@
style="font-size:12.76000023px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
xml:space="preserve"
id="text34203"
- y="544.36218"
+ y="364.36218"
x="20"><tspan
- y="544.36218"
+ y="364.36218"
x="20"
id="tspan16197"
sodipodi:role="line">%% Add Delete Disabled Enabled HideVars IECCDown IECCUp Maximize Minimize minus plus ShowVars %%</tspan></text>
@@ -83561,7 +83691,7 @@
transform="matrix(4.5011397,0,0,4.5011397,2971.834,-119.97324)" />
<g
id="g18981"
- transform="translate(25.999952,123.97794)">
+ transform="translate(25.999952,-56.02206)">
<rect
width="15.999955"
height="15.999955"
@@ -83604,7 +83734,7 @@
transform="matrix(3.9071406,0,0,3.9071406,2402.8076,-50.595777)" />
<g
id="g18994"
- transform="translate(19.498009,119.61597)">
+ transform="translate(19.498009,-60.38403)">
<rect
width="16"
height="16"
@@ -83630,7 +83760,7 @@
transform="matrix(1.0031449,0,0,1.0031449,685.39009,256.82525)" />
<g
id="g19000"
- transform="matrix(0.9968618,0,0,0.996865,-396.72428,99.913609)">
+ transform="matrix(0.9968618,0,0,0.996865,-396.72428,-80.086391)">
<rect
width="16.050318"
height="16.050318"
@@ -83648,7 +83778,7 @@
transform="matrix(1.0031449,0,0,1.0031449,660.09588,272.46095)" />
<g
id="g19004"
- transform="matrix(0.9968618,0,0,0.9968618,-339.72428,79.915124)">
+ transform="matrix(0.9968618,0,0,0.9968618,-339.72428,-100.08488)">
<rect
width="16.050318"
height="16.050318"
@@ -83663,7 +83793,7 @@
</g>
<g
id="g19086"
- transform="matrix(0.965737,0,0,0.965737,-233.99669,79.166717)">
+ transform="matrix(0.965737,0,0,0.965737,-233.99669,-100.83328)">
<rect
width="24.075478"
height="24.075478"
@@ -83938,14 +84068,14 @@
<rect
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="IECCUp"
- y="551.36218"
+ y="371.36218"
x="380"
height="16"
width="16" />
<g
style="fill:url(#linearGradient16279);fill-opacity:1;stroke:url(#linearGradient16281)"
id="g59085"
- transform="matrix(1,0,0,-1,-6.571463,804.20104)">
+ transform="matrix(1,0,0,-1,-6.571463,624.20104)">
<path
style="fill:url(#linearGradient16274);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient16276);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2160"
@@ -83954,14 +84084,14 @@
<rect
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="IECCDown"
- y="551.36218"
+ y="371.36218"
x="334"
height="16"
width="16" />
<g
style="fill:url(#linearGradient16270);fill-opacity:1;stroke:url(#linearGradient16272)"
id="g59118"
- transform="translate(39.428537,314.5234)">
+ transform="translate(39.428537,134.5234)">
<path
style="fill:url(#linearGradient16266);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient16268);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path59120"
@@ -83969,7 +84099,7 @@
</g>
<g
id="g19018"
- transform="matrix(0.9968629,0,0,0.9968629,-119.72484,1.91483)">
+ transform="matrix(0.9968629,0,0,0.9968629,-119.72484,-178.08517)">
<rect
width="24.075478"
height="24.075478"
@@ -84175,7 +84305,7 @@
</g>
<g
id="g19359"
- transform="translate(20,119.91553)">
+ transform="translate(20,-60.08447)">
<rect
width="23.999916"
height="23.999916"
@@ -84307,7 +84437,7 @@
</g>
<g
id="g19146"
- transform="matrix(0.996861,0,0,0.996861,17.276127,-43.08392)">
+ transform="matrix(0.996861,0,0,0.996861,17.276127,-223.08392)">
<rect
width="13.040884"
height="13.040884"
@@ -84336,7 +84466,7 @@
</g>
<g
id="g19152"
- transform="matrix(0.996861,0,0,0.996861,58.276127,-63.08387)">
+ transform="matrix(0.996861,0,0,0.996861,58.276127,-243.08387)">
<rect
width="13.040884"
height="13.040884"
@@ -84375,29 +84505,29 @@
style="font-size:12.76000023px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
xml:space="preserve"
id="text60407"
- y="477.44528"
- x="310.27524"><tspan
- y="477.44528"
- x="310.27524"
+ y="257.44528"
+ x="170.27524"><tspan
+ y="257.44528"
+ x="170.27524"
id="tspan16195"
sodipodi:role="line">%% Compiler TargetType %%</tspan></text>
<rect
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="Compiler"
- y="487.36218"
- x="360"
+ y="267.36218"
+ x="220"
height="24"
width="24" />
<rect
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="TargetType"
- y="488.43768"
- x="430"
+ y="268.43768"
+ x="290"
height="23.999981"
width="23.999981" />
<g
id="g23927"
- transform="matrix(9.6211589e-2,0,0,9.6211589e-2,359.72267,486.8136)">
+ transform="matrix(9.6211589e-2,0,0,9.6211589e-2,219.72267,266.8136)">
<g
id="g23929"
transform="translate(48.379983,78.100302)" />
@@ -84440,7 +84570,7 @@
</g>
<g
id="g19199"
- transform="matrix(5.3097304e-2,0,0,5.3097304e-2,387.38564,480.36282)">
+ transform="matrix(5.3097304e-2,0,0,5.3097304e-2,247.38564,260.36282)">
<circle
sodipodi:ry="226"
sodipodi:rx="226"
@@ -84501,44 +84631,44 @@
style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text18377"
- y="154.61037"
+ y="134.61037"
x="42.860386"><tspan
id="tspan18379"
- y="154.61037"
+ y="134.61037"
x="42.860386">Plugin</tspan><tspan
id="tspan18381"
- y="179.61037"
+ y="159.61037"
x="42.860386">Methods</tspan></text>
<text
style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text18383"
- y="154.61037"
- x="283.48929"><tspan
+ y="254.61035"
+ x="43.489288"><tspan
id="tspan18385"
- y="154.61037"
- x="283.48929">Plugin</tspan><tspan
+ y="254.61035"
+ x="43.489288">Plugin</tspan><tspan
id="tspan18387"
- y="179.61037"
- x="283.48929">Params</tspan></text>
+ y="279.61035"
+ x="43.489288">Params</tspan></text>
<text
style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text18389"
- y="154.61037"
- x="504.30698"><tspan
+ y="333.61218"
+ x="37.5"><tspan
id="tspan18393"
- y="154.61037"
- x="504.30698">Buttons</tspan></text>
+ y="333.61218"
+ x="37.5">Buttons</tspan></text>
<text
style="font-size:40.12579727px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text18397"
- y="74.610374"
+ y="54.610374"
x="371.85562"><tspan
style="text-align:center;text-anchor:middle"
id="tspan18399"
- y="74.610374"
+ y="54.610374"
x="371.85562">Beremiz icons</tspan></text>
<g
id="g18993"
@@ -84630,19 +84760,19 @@
x="-988.61249">Pre-Alpha Release. Copyright © LOLITECH 2008</tspan></text>
</g>
<text
- style="font-size:12.76000023px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ style="font-size:51.04000092px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text18989"
- y="771.29181"
- x="323.28137"><tspan
+ y="761.85242"
+ x="176.98375"><tspan
id="tspan18991"
- y="771.29181"
- x="323.28137"
- style="font-size:12.76000023px">%% splash %%</tspan></text>
+ y="761.85242"
+ x="176.98375"
+ style="font-size:51.04000092px">%% splash %%</tspan></text>
<g
style="display:inline"
id="g19354"
- transform="matrix(0.2686638,0,0,0.2686638,754.93573,100.70118)">
+ transform="matrix(0.2686638,0,0,0.2686638,514.93573,-19.29882)">
<g
id="g19356"
mask="url(#mask6467)"
@@ -84748,13 +84878,13 @@
style="font-size:13.88476658px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"
id="text19983"
- y="629.33417"
- x="266.00839"><tspan
+ y="509.33417"
+ x="26.008392"><tspan
id="tspan19985"
- y="629.33417"
- x="266.00839">%% ico48 ico24 ico16 %%</tspan></text>
+ y="509.33417"
+ x="26.008392">%% ico48 ico24 ico16 %%</tspan></text>
<g
- transform="translate(1084.009,-113.72536)"
+ transform="translate(1084.009,-193.72536)"
id="g17590">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
@@ -84835,7 +84965,7 @@
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
<g
- transform="translate(1083.788,-113.94633)"
+ transform="translate(1083.788,-193.94633)"
style="fill:#4fadf7;fill-opacity:1"
id="g17603">
<rect
@@ -84917,7 +85047,7 @@
y="404.23816" />
</g>
<path
- transform="translate(-0.212,-7.035e-2)"
+ transform="translate(-0.212,-80.07035)"
d="M 263.65515,289.22899 L 258.6897,286.36218 L 263.65515,283.49538 L 263.65515,289.22899 z"
inkscape:randomized="0"
inkscape:rounded="0"
@@ -84947,9 +85077,9 @@
inkscape:rounded="0"
inkscape:randomized="0"
d="M 263.65515,289.22899 L 258.6897,286.36218 L 263.65515,283.49538 L 263.65515,289.22899 z"
- transform="translate(-0.5214,-0.3797)" />
+ transform="translate(-0.5214,-80.3797)" />
<g
- transform="matrix(8.8340245e-2,0,0,8.8340245e-2,320.46956,254.13123)"
+ transform="matrix(8.8340245e-2,0,0,8.8340245e-2,320.46956,174.13123)"
id="g17968">
<g
style="display:inline"
@@ -85051,27 +85181,27 @@
transform="matrix(0.5324675,0,0,0.5324675,-889.75288,329.57107)" />
</g>
<text
- x="33.295933"
- y="341.52582"
+ x="113.29593"
+ y="121.52582"
id="text16266"
xml:space="preserve"
style="font-size:12.76095104px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan16268"
- x="33.295933"
- y="341.52582">%% editIECrawcode EditCfile %%</tspan></text>
+ x="113.29593"
+ y="121.52582">%% editIECrawcode EditCfile Transfer Connect Disconnect Debug %%</tspan></text>
<rect
width="24"
height="24"
- x="60"
- y="371.36218"
+ x="140"
+ y="131.36218"
id="editIECrawcode"
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
inkscape:label="#rect16270" />
<g
id="g20019"
- transform="translate(-600.13257,100)">
+ transform="translate(-520.13257,-140)">
<flowRoot
transform="matrix(1.6473499,0,0,1.6473499,800.92342,263.57576)"
id="flowRoot19870"
@@ -85283,12 +85413,12 @@
inkscape:label="#rect16270"
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="EditCfile"
- y="371.36218"
- x="180"
+ y="131.36218"
+ x="260"
height="24"
width="24" />
<g
- transform="translate(-480.13257,100)"
+ transform="translate(-400.13257,-140)"
id="g20864">
<flowRoot
style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient20956);fill-opacity:1;stroke:#547c1b;stroke-width:0.1061436;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Andale Mono"
@@ -85497,4 +85627,81 @@
d="M 683.74504,273.49232 C 682.52118,272.4238 682.05427,273.42101 681.27718,274.09784 C 681.11122,273.95433 680.94525,273.81082 680.77929,273.66732 C 681.36055,272.97034 681.65306,272.60698 682.5725,272.17027 C 682.75266,272.27845 683.47211,273.29694 683.74504,273.49232 z"
style="fill:url(#linearGradient20979);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
+ <text
+ x="37.5"
+ y="473.61218"
+ id="text16382"
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><tspan
+ x="37.5"
+ y="473.61218"
+ id="tspan16384">Icons</tspan></text>
+ <rect
+ width="24"
+ height="24"
+ x="320"
+ y="131.36218"
+ id="Transfer"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ inkscape:label="#rect16270" />
+ <rect
+ inkscape:label="#rect16270"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="Connect"
+ y="131.36218"
+ x="380"
+ height="24"
+ width="24" />
+ <rect
+ width="24"
+ height="24"
+ x="440"
+ y="131.36218"
+ id="Disconnect"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ inkscape:label="#rect16270" />
+ <path
+ style="opacity:1;fill:url(#linearGradient17534);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:8.59499931;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 392,137.40625 C 388.95578,137.61016 386.40951,139.60837 385.4375,142.375 L 380,142.375 L 380,147.375 L 385.4375,147.375 C 386.41696,150.12787 388.96436,152.1385 392,152.34375 L 392,137.40625 z M 393,137.40625 L 393,152.34375 C 396.03564,152.1385 398.58304,150.12787 399.5625,147.375 L 404,147.375 L 404,142.375 L 399.5625,142.375 C 398.59049,139.60837 396.04422,137.61016 393,137.40625 z"
+ id="path16742" />
+ <path
+ style="opacity:1;fill:url(#linearGradient17532);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:8.59499931;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 448,137.40625 C 444.95578,137.61016 442.40951,139.60837 441.4375,142.375 L 436,142.375 L 436,147.375 L 441.4375,147.375 C 442.41696,150.12787 444.96436,152.1385 448,152.34375 L 448,137.40625 z"
+ id="path16754" />
+ <path
+ style="opacity:1;fill:url(#linearGradient17536);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:8.59499931;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 456,137.42468 L 456,152.36218 C 459.03564,152.15693 461.58304,150.1463 462.5625,147.39343 L 467,147.39343 L 467,142.39343 L 462.5625,142.39343 C 461.59049,139.6268 459.04422,137.62859 456,137.42468 z"
+ id="path16750" />
+ <path
+ id="path17538"
+ d="M 332,137.40625 C 328.95578,137.61016 326.40951,139.60837 325.4375,142.375 L 320,142.375 L 320,147.375 L 325.4375,147.375 C 326.41696,150.12787 328.96436,152.1385 332,152.34375 L 332,137.40625 z M 333,137.40625 L 333,152.34375 C 336.03564,152.1385 338.58304,150.12787 339.5625,147.375 L 344,147.375 L 344,142.375 L 339.5625,142.375 C 338.59049,139.60837 336.04422,137.61016 333,137.40625 z"
+ style="opacity:1;fill:url(#linearGradient17540);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:8.59499931;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:url(#linearGradient17552);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:8.59499931;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path17544"
+ sodipodi:sides="3"
+ sodipodi:cx="332"
+ sodipodi:cy="144.36218"
+ sodipodi:r1="5.6378174"
+ sodipodi:r2="2.8189087"
+ sodipodi:arg1="1.5707963"
+ sodipodi:arg2="2.6179939"
+ inkscape:flatsided="true"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 332,150 L 327.11751,141.54327 L 336.88249,141.54327 L 332,150 z"
+ transform="matrix(1.1031299,0.6368924,-0.6368924,1.1031299,58.022874,-226.14748)" />
+ <rect
+ inkscape:label="#rect16270"
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="Debug"
+ y="131.36218"
+ x="500"
+ height="24"
+ width="24" />
+ <path
+ style="opacity:1;fill:#160379;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 514.14211,134.18201 L 513.44846,134.37119 L 514.09481,136.75167 C 513.55664,136.88976 513.034,137.15037 512.61293,137.57144 C 512.58031,137.60404 512.56503,137.64813 512.53409,137.68179 C 511.78169,137.61864 511.00589,137.70771 510.24821,137.94979 L 509.60186,135.53777 L 508.90819,135.72696 L 509.5861,138.20203 C 508.90904,138.50465 508.25523,138.92751 507.67856,139.47899 L 505.51876,138.13898 L 505.15618,138.7538 L 507.18983,139.99922 C 506.46851,140.85118 505.98679,141.84216 505.771,142.83687 L 503.56394,142.23781 L 503.37475,142.93146 L 505.67641,143.56206 C 505.54986,144.9582 505.92466,146.33767 506.82724,147.39291 L 514.37857,139.84157 L 514.88305,140.34605 L 507.33172,147.89738 C 508.2984,148.72357 509.54404,149.1165 510.81574,149.07974 L 511.28869,150.84539 L 511.98234,150.65621 L 511.54092,149.01668 C 512.59832,148.85042 513.66579,148.38127 514.58351,147.64515 L 515.46634,149.06397 L 516.08117,148.70138 L 515.13529,147.15642 C 515.82639,146.46532 516.32697,145.65819 516.64871,144.82323 L 518.49319,145.31195 L 518.68237,144.6183 L 516.86941,144.1296 C 517.03506,143.48333 517.09629,142.82751 517.04282,142.19052 C 517.07648,142.15959 517.12057,142.14431 517.15319,142.11171 C 517.57426,141.69063 517.83486,141.16798 517.97296,140.6298 L 520.35344,141.27617 L 520.54262,140.58251 L 518.08331,139.92038 C 518.10481,139.07152 517.80055,138.21881 517.15319,137.57144 C 516.50581,136.92407 515.6531,136.61982 514.80424,136.64131 L 514.14211,134.18201 z"
+ id="path16411" />
</svg>
--- a/plugger.py Tue Aug 12 16:27:07 2008 +0200
+++ b/plugger.py Wed Aug 20 00:11:40 2008 +0200
@@ -70,6 +70,10 @@
def BufferProject(self):
pass
+# helper func to get path to images
+def opjimg(imgname):
+ return os.path.join("images",imgname)
+
class PlugTemplate:
"""
This class is the one that define plugins.
@@ -100,9 +104,6 @@
# copy PluginMethods so that it can be later customized
self.PluginMethods = [dic.copy() for dic in self.PluginMethods]
- def IsGUIPlugin(self):
- return False
-
def PluginBaseXmlFilePath(self, PlugName=None):
return os.path.join(self.PlugPath(PlugName), "baseplugin.xml")
@@ -112,7 +113,8 @@
def PlugPath(self,PlugName=None):
if not PlugName:
PlugName = self.BaseParams.getName()
- return os.path.join(self.PlugParent.PlugPath(), PlugName + NameTypeSeparator + self.PlugType)
+ return os.path.join(self.PlugParent.PlugPath(),
+ PlugName + NameTypeSeparator + self.PlugType)
def PlugTestModified(self):
return self.ChangesToSave
@@ -149,13 +151,13 @@
params.append(self.PlugParams[1].getElementInfos(self.PlugParams[0]))
return params
- def SetParamsAttribute(self, path, value, logger):
+ def SetParamsAttribute(self, path, value):
self.ChangesToSave = True
# Filter IEC_Channel and Name, that have specific behavior
if path == "BaseParams.IEC_Channel":
- return self.FindNewIEC_Channel(value,logger), True
+ return self.FindNewIEC_Channel(value), True
elif path == "BaseParams.Name":
- res = self.FindNewName(value,logger)
+ res = self.FindNewName(value)
self.PlugRequestSave()
return res, True
@@ -205,7 +207,7 @@
shutil.copytree(src_PlugPath, self.PlugPath)
return True
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
"""
Generate C code
@param locations: List of complete variables locations \
@@ -217,12 +219,15 @@
}, ...]
@return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
"""
- logger.write_warning(".".join(map(lambda x:str(x), self.GetCurrentLocation())) + " -> Nothing to do\n")
+ self.logger.write_warning(".".join(map(lambda x:str(x), self.GetCurrentLocation())) + " -> Nothing to do\n")
return [],"",False
- def _Generate_C(self, buildpath, locations, logger):
- # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS
- PlugCFilesAndCFLAGS, PlugLDFLAGS, DoCalls = self.PlugGenerate_C(buildpath, locations, logger)
+ def _Generate_C(self, buildpath, locations):
+ # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS, DoCalls, extra_files
+ # extra_files = [(fname,fobject), ...]
+ gen_result = self.PlugGenerate_C(buildpath, locations)
+ PlugCFilesAndCFLAGS, PlugLDFLAGS, DoCalls = gen_result[:3]
+ extra_files = gen_result[3:]
# if some files heve been generated put them in the list with their location
if PlugCFilesAndCFLAGS:
LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), PlugCFilesAndCFLAGS, DoCalls)]
@@ -245,19 +250,18 @@
new_location = PlugChild.GetCurrentLocation()
# How deep are we in the tree ?
depth=len(new_location)
- _LocationCFilesAndCFLAGS, _LDFLAGS = \
+ _LocationCFilesAndCFLAGS, _LDFLAGS, _extra_files = \
PlugChild._Generate_C(
#keep the same path
buildpath,
# filter locations that start with current IEC location
- [loc for loc in locations if loc["LOC"][0:depth] == new_location ],
- #propagete logger
- logger)
+ [loc for loc in locations if loc["LOC"][0:depth] == new_location ])
# stack the result
LocationCFilesAndCFLAGS += _LocationCFilesAndCFLAGS
LDFLAGS += _LDFLAGS
-
- return LocationCFilesAndCFLAGS,LDFLAGS
+ extra_files += _extra_files
+
+ return LocationCFilesAndCFLAGS, LDFLAGS, extra_files
def BlockTypesFactory(self):
return []
@@ -343,7 +347,7 @@
else:
return {"name" : self.BaseParams.getName(), "channel" : self.BaseParams.getIEC_Channel(), "enabled" : self.BaseParams.getEnabled(), "parent" : len(self.PlugChildsTypes) > 0, "type" : self.BaseParams.getName(), "values" : childs}
- def FindNewName(self, DesiredName, logger):
+ def FindNewName(self, DesiredName):
"""
Changes Name to DesiredName if available, Name-N if not.
@param DesiredName: The desired Name (string)
@@ -376,10 +380,10 @@
shutil.move(oldname, self.PlugPath())
# warn user he has two left hands
if DesiredName != res:
- logger.write_warning("A child names \"%s\" already exist -> \"%s\"\n"%(DesiredName,res))
+ self.logger.write_warning("A child names \"%s\" already exist -> \"%s\"\n"%(DesiredName,res))
return res
- def FindNewIEC_Channel(self, DesiredChannel, logger):
+ def FindNewIEC_Channel(self, DesiredChannel):
"""
Changes IEC Channel number to DesiredChannel if available, nearest available if not.
@param DesiredChannel: The desired IEC channel (int)
@@ -401,15 +405,14 @@
if res < CurrentChannel: # Want to go down ?
res -= 1 # Test for n-1
if res < 0 :
- if logger :
- logger.write_warning("Cannot find lower free IEC channel than %d\n"%CurrentChannel)
+ self.logger.write_warning("Cannot find lower free IEC channel than %d\n"%CurrentChannel)
return CurrentChannel # Can't go bellow 0, do nothing
else : # Want to go up ?
res += 1 # Test for n-1
# Finally set IEC Channel
self.BaseParams.setIEC_Channel(res)
- if logger and DesiredChannel != res:
- logger.write_warning("A child with IEC channel %d already exist -> %d\n"%(DesiredChannel,res))
+ if DesiredChannel != res:
+ self.logger.write_warning("A child with IEC channel %d already exist -> %d\n"%(DesiredChannel,res))
return res
def OnPlugClose(self):
@@ -433,7 +436,7 @@
# Ask to his parent to remove it
self.PlugParent._doRemoveChild(self)
- def PlugAddChild(self, PlugName, PlugType, logger):
+ def PlugAddChild(self, PlugName, PlugType):
"""
Create the plugins that may be added as child to this node self
@param PlugType: string desining the plugin class name (get name from PlugChildsTypes)
@@ -469,6 +472,8 @@
def __init__(_self):
# self is the parent
_self.PlugParent = self
+ # self is the parent
+ _self.logger = self.logger
# Keep track of the plugin type name
_self.PlugType = PlugType
# remind the help string, for more fancy display
@@ -476,11 +481,11 @@
# Call the base plugin template init - change XSD into class members
PlugTemplate.__init__(_self)
# check name is unique
- NewPlugName = _self.FindNewName(PlugName, logger)
+ NewPlugName = _self.FindNewName(PlugName)
# If dir have already be made, and file exist
if os.path.isdir(_self.PlugPath(NewPlugName)): #and os.path.isfile(_self.PluginXmlFilePath(PlugName)):
#Load the plugin.xml file into parameters members
- _self.LoadXMLParams(logger, NewPlugName)
+ _self.LoadXMLParams(NewPlugName)
# Basic check. Better to fail immediately.
if (_self.BaseParams.getName() != NewPlugName):
raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(NewPlugName, _self.BaseParams.getName())
@@ -488,12 +493,12 @@
# Now, self.PlugPath() should be OK
# Check that IEC_Channel is not already in use.
- _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel(),logger)
+ _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel())
# Call the plugin real __init__
if getattr(PlugClass, "__init__", None):
PlugClass.__init__(_self)
#Load and init all the childs
- _self.LoadChilds(logger)
+ _self.LoadChilds()
#just loaded, nothing to saved
_self.ChangesToSave = False
else:
@@ -519,42 +524,44 @@
return newPluginOpj
- def LoadXMLParams(self, logger, PlugName = None):
+ def LoadXMLParams(self, PlugName = None):
methode_name = os.path.join(self.PlugPath(PlugName), "methods.py")
if os.path.isfile(methode_name):
execfile(methode_name)
# Get the base xml tree
if self.MandatoryParams:
- #try:
+ try:
basexmlfile = open(self.PluginBaseXmlFilePath(PlugName), 'r')
basetree = minidom.parse(basexmlfile)
self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0])
basexmlfile.close()
- #except Exception, e:
- # logger.write_error("Couldn't load plugin base parameters %s :\n %s" % (PlugName, str(e)))
-
+ except Exception, exc:
+ self.logger.write_error("Couldn't load plugin base parameters %s :\n %s" % (PlugName, str(exc)))
+ self.logger.write_error(traceback.format_exc())
# Get the xml tree
if self.PlugParams:
- #try:
+ try:
xmlfile = open(self.PluginXmlFilePath(PlugName), 'r')
tree = minidom.parse(xmlfile)
self.PlugParams[1].loadXMLTree(tree.childNodes[0])
xmlfile.close()
- #except Exception, e:
- # logger.write_error("Couldn't load plugin parameters %s :\n %s" % (PlugName, str(e)))
-
- def LoadChilds(self, logger):
+ except Exception, exc:
+ self.logger.write_error("Couldn't load plugin parameters %s :\n %s" % (PlugName, str(exc)))
+ self.logger.write_error(traceback.format_exc())
+
+ def LoadChilds(self):
# Iterate over all PlugName@PlugType in plugin directory, and try to open them
for PlugDir in os.listdir(self.PlugPath()):
if os.path.isdir(os.path.join(self.PlugPath(), PlugDir)) and \
PlugDir.count(NameTypeSeparator) == 1:
pname, ptype = PlugDir.split(NameTypeSeparator)
- #try:
- self.PlugAddChild(pname, ptype, logger)
- #except Exception, e:
- # logger.write_error("Could not add child \"%s\", type %s :\n%s\n"%(pname, ptype, str(e)))
+ try:
+ self.PlugAddChild(pname, ptype)
+ except Exception, exc:
+ self.logger.write_error("Could not add child \"%s\", type %s :\n%s\n"%(pname, ptype, str(exc)))
+ self.logger.write_error(traceback.format_exc())
def EnableMethod(self, method, value):
for d in self.PluginMethods:
@@ -563,6 +570,13 @@
return True
return False
+ def ShowMethod(self, method, value):
+ for d in self.PluginMethods:
+ if d["method"]==method:
+ d["shown"]=value
+ return True
+ return False
+
def _GetClassFunction(name):
def GetRootClass():
return getattr(__import__("plugins." + name), name).RootClass
@@ -586,15 +600,24 @@
ieclib_path = os.path.join(base_folder, "matiec", "lib")
# import for project creation timestamping
+from threading import Timer
from time import localtime
from datetime import datetime
# import necessary stuff from PLCOpenEditor
from PLCControler import PLCControler
from PLCOpenEditor import PLCOpenEditor, ProjectDialog
from TextViewer import TextViewer
-from plcopen.structures import IEC_KEYWORDS
+from plcopen.structures import IEC_KEYWORDS, TypeHierarchy_list
+
+# Construct debugger natively supported types
+DebugTypes = [t for t in zip(*TypeHierarchy_list)[0] if not t.startswith("ANY")] + \
+ ["STEP","TRANSITION","ACTION"]
+
import runtime
import re
+import targets
+import connectors
+from discovery import DiscoveryDialog
class PluginsRoot(PlugTemplate, PLCControler):
"""
@@ -619,77 +642,36 @@
<xsd:element name="TargetType">
<xsd:complexType>
<xsd:choice>
- <xsd:element name="Win32">
- <xsd:complexType>
- <xsd:attribute name="Priority" type="xsd:integer" use="required"/>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Linux">
- <xsd:complexType>
- <xsd:attribute name="Nice" type="xsd:integer" use="required"/>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Xenomai">
- <xsd:complexType>
- <xsd:attribute name="xeno_config" type="xsd:string" use="optional" default="/usr/xenomai/"/>
- <xsd:attribute name="Priority" type="xsd:integer" use="required"/>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="RTAI">
- <xsd:complexType>
- <xsd:attribute name="rtai_config" type="xsd:string" use="required"/>
- <xsd:attribute name="Priority" type="xsd:integer" use="required"/>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Library">
- <xsd:complexType>
- <xsd:attribute name="Dynamic" type="xsd:boolean" use="optional" default="true"/>
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Connection">
- <xsd:complexType>
- <xsd:choice>
- <xsd:element name="Local"/>
- <xsd:element name="TCP_IP">
- <xsd:complexType>
- <xsd:attribute name="Host" type="xsd:string" use="required"/>
- </xsd:complexType>
- </xsd:element>
+ """+targets.targetchoices+"""
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
- <xsd:attribute name="Compiler" type="xsd:string" use="optional" default="gcc"/>
- <xsd:attribute name="CFLAGS" type="xsd:string" use="required"/>
- <xsd:attribute name="Linker" type="xsd:string" use="optional" default="ld"/>
- <xsd:attribute name="LDFLAGS" type="xsd:string" use="required"/>
- <xsd:attribute name="Sync_Align_Ratio" use="optional" default="50">
- <xsd:simpleType>
- <xsd:restriction base="xsd:integer">
- <xsd:minInclusive value="1"/>
- <xsd:maxInclusive value="99"/>
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
"""
- def __init__(self, frame):
+ def __init__(self, frame, logger):
PLCControler.__init__(self)
self.MandatoryParams = None
self.AppFrame = frame
-
- """
- This method are not called here... but in NewProject and OpenProject
- self._AddParamsMembers()
- self.PluggedChilds = {}
- """
+ self.logger = logger
+ self._builder = None
+ self._connector = None
+
+ # Setup debug information
+ self.IECdebug_callables = {}
+ # Timer to prevent rapid-fire when registering many variables
+ self.DebugTimer=Timer(0.5,self.RegisterDebugVarToConnector)
+ self.ResetIECProgramsAndVariables()
+
+
+ #This method are not called here... but in NewProject and OpenProject
+ #self._AddParamsMembers()
+ #self.PluggedChilds = {}
+
# In both new or load scenario, no need to save
self.ChangesToSave = False
# root have no parent
@@ -769,14 +751,14 @@
self.SaveProject()
return None
- def LoadProject(self, ProjectPath, logger):
+ def LoadProject(self, ProjectPath):
"""
Load a project contained in a folder
@param ProjectPath: path of the project folder
"""
if os.path.basename(ProjectPath) == "":
ProjectPath = os.path.dirname(ProjectPath)
- # Verify that project contains a PLCOpen program
+ # Verify that project contains a PLCOpen program
plc_file = os.path.join(ProjectPath, "plc.xml")
if not os.path.isfile(plc_file):
return "Folder choosen doesn't contain a program. It's not a valid project!"
@@ -792,12 +774,19 @@
# If dir have already be made, and file exist
if os.path.isdir(self.PlugPath()) and os.path.isfile(self.PluginXmlFilePath()):
#Load the plugin.xml file into parameters members
- result = self.LoadXMLParams(logger)
+ result = self.LoadXMLParams()
if result:
return result
#Load and init all the childs
- self.LoadChilds(logger)
+ self.LoadChilds()
self.RefreshPluginsBlockLists()
+
+ if os.path.exists(self._getBuildPath()):
+ self.EnableMethod("_Clean", True)
+
+ if os.path.isfile(self._getIECrawcodepath()):
+ self.ShowMethod("_showIECcode", True)
+
return None
def SaveProject(self):
@@ -827,19 +816,12 @@
def PluginXmlFilePath(self, PlugName=None):
return os.path.join(self.PlugPath(PlugName), "beremiz.xml")
- def PlugGenerate_C(self, buildpath, locations, logger):
- """
- Generate C code
- @param locations: List of complete variables locations \
- [(IEC_loc, IEC_Direction, IEC_Type, Name)]\
- ex: [((0,0,4,5),'I','STRING','__IX_0_0_4_5'),...]
- @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
- """
- return [(C_file_name, self.CFLAGS) for C_file_name in self.PLCGeneratedCFiles ] , "", False
-
def _getBuildPath(self):
return os.path.join(self.ProjectPath, "build")
+ def _getExtraFilesPath(self):
+ return os.path.join(self._getBuildPath(), "extra_files")
+
def _getIECcodepath(self):
# define name for IEC code file
return os.path.join(self._getBuildPath(), "plc.st")
@@ -850,7 +832,7 @@
def _getIECrawcodepath(self):
# define name for IEC raw code file
- return os.path.join(self._getBuildPath(), "raw_plc.st")
+ return os.path.join(self.PlugPath(), "raw_plc.st")
def GetLocations(self):
locations = []
@@ -877,23 +859,22 @@
locations.append(resdict)
return locations
- def _Generate_SoftPLC(self, logger):
+ def _Generate_SoftPLC(self):
"""
Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C
@param buildpath: path where files should be created
- @param logger: the log pseudo file
"""
# Update PLCOpenEditor Plugin Block types before generate ST code
self.RefreshPluginsBlockLists()
- logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n")
+ self.logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n")
buildpath = self._getBuildPath()
# ask PLCOpenEditor controller to write ST/IL/SFC code file
result = self.GenerateProgram(self._getIECgeneratedcodepath())
if result is not None:
# Failed !
- logger.write_error("Error in ST/IL/SFC code generator :\n%s\n"%result)
+ self.logger.write_error("Error in ST/IL/SFC code generator :\n%s\n"%result)
return False
plc_file = open(self._getIECcodepath(), "w")
if os.path.isfile(self._getIECrawcodepath()):
@@ -901,11 +882,11 @@
plc_file.write("\n")
plc_file.write(open(self._getIECgeneratedcodepath(), "r").read())
plc_file.close()
- logger.write("Compiling IEC Program in to C code...\n")
+ self.logger.write("Compiling IEC Program in to C code...\n")
# Now compile IEC code into many C files
# files are listed to stdout, and errors to stderr.
status, result, err_result = ProcessLogger(
- logger,
+ self.logger,
"\"%s\" -f \"%s\" -I \"%s\" \"%s\""%(
iec2c_path,
self._getIECcodepath(),
@@ -913,27 +894,238 @@
no_stdout=True).spin()
if status:
# Failed !
- logger.write_error("Error : IEC to C compiler returned %d\n"%status)
+ self.logger.write_error("Error : IEC to C compiler returned %d\n"%status)
return False
# Now extract C files of stdout
C_files = [ fname for fname in result.splitlines() if fname[-2:]==".c" or fname[-2:]==".C" ]
# remove those that are not to be compiled because included by others
C_files.remove("POUS.c")
if not C_files:
- logger.write_error("Error : At least one configuration and one ressource must be declared in PLC !\n")
+ self.logger.write_error("Error : At least one configuration and one ressource must be declared in PLC !\n")
return False
# transform those base names to full names with path
C_files = map(lambda filename:os.path.join(buildpath, filename), C_files)
- logger.write("Extracting Located Variables...\n")
+ self.logger.write("Extracting Located Variables...\n")
# Keep track of generated located variables for later use by self._Generate_C
self.PLCGeneratedLocatedVars = self.GetLocations()
# Keep track of generated C files for later use by self.PlugGenerate_C
self.PLCGeneratedCFiles = C_files
# compute CFLAGS for plc
- self.CFLAGS = "\"-I"+ieclib_path+"\""
+ self.plcCFLAGS = "\"-I"+ieclib_path+"\""
return True
- def _build(self, logger):
+ def GetBuilder(self):
+ """
+ Return a Builder (compile C code into machine code)
+ """
+ # Get target, module and class name
+ targetname = self.BeremizRoot.getTargetType().getcontent()["name"]
+ modulename = "targets." + targetname
+ classname = targetname + "_target"
+
+ # Get module reference
+ try :
+ targetmodule = getattr(__import__(modulename), targetname)
+
+ except Exception, msg:
+ self.logger.write_error("Can't find module for target %s!\n"%targetname)
+ self.logger.write_error(str(msg))
+ return None
+
+ # Get target class
+ targetclass = getattr(targetmodule, classname)
+
+ # if target already
+ if self._builder is None or not isinstance(self._builder,targetclass):
+ # Get classname instance
+ self._builder = targetclass(self)
+ return self._builder
+
+ def GetLastBuildMD5(self):
+ builder=self.GetBuilder()
+ if builder is not None:
+ return builder.GetBinaryCodeMD5()
+ else:
+ return None
+
+ #######################################################################
+ #
+ # C CODE GENERATION METHODS
+ #
+ #######################################################################
+
+ def PlugGenerate_C(self, buildpath, locations):
+ """
+ Return C code generated by iec2c compiler
+ when _generate_softPLC have been called
+ @param locations: ignored
+ @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
+ """
+ return [(C_file_name, self.plcCFLAGS) for C_file_name in self.PLCGeneratedCFiles ] , "-lrt", False
+
+ def ResetIECProgramsAndVariables(self):
+ """
+ Reset variable and program list that are parsed from
+ CSV file generated by IEC2C compiler.
+ """
+ self._ProgramList = None
+ self._VariablesList = None
+ self._IECPathToIdx = None
+ self._IdxToIECPath = None
+
+ def GetIECProgramsAndVariables(self):
+ """
+ Parse CSV-like file VARIABLES.csv resulting from IEC2C compiler.
+ Each section is marked with a line staring with '//'
+ list of all variables used in various POUs
+ """
+ if self._ProgramList is None or self._VariablesList is None:
+ try:
+ csvfile = os.path.join(self._getBuildPath(),"VARIABLES.csv")
+ # describes CSV columns
+ ProgramsListAttributeName = ["num", "C_path", "type"]
+ VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
+ self._ProgramList = []
+ self._VariablesList = []
+ self._IECPathToIdx = {}
+ self._IdxToIECPath = {}
+
+ # Separate sections
+ ListGroup = []
+ for line in open(csvfile,'r').xreadlines():
+ strippedline = line.strip()
+ if strippedline.startswith("//"):
+ # Start new section
+ ListGroup.append([])
+ elif len(strippedline) > 0 and len(ListGroup) > 0:
+ # append to this section
+ ListGroup[-1].append(strippedline)
+
+ # first section contains programs
+ for line in ListGroup[0]:
+ # Split and Maps each field to dictionnary entries
+ attrs = dict(zip(ProgramsListAttributeName,line.strip().split(';')))
+ # Truncate "C_path" to remove conf an ressources names
+ attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:])
+ # Push this dictionnary into result.
+ self._ProgramList.append(attrs)
+
+ # second section contains all variables
+ for line in ListGroup[1]:
+ # Split and Maps each field to dictionnary entries
+ attrs = dict(zip(VariablesListAttributeName,line.strip().split(';')))
+ # Truncate "C_path" to remove conf an ressources names
+ attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:])
+ # Push this dictionnary into result.
+ self._VariablesList.append(attrs)
+ # Fill in IEC<->C translation dicts
+ IEC_path=attrs["IEC_path"]
+ Idx=int(attrs["num"])
+ self._IECPathToIdx[IEC_path]=Idx
+ self._IdxToIECPath[Idx]=IEC_path
+ except Exception,e:
+ self.logger.write_error("Cannot open/parse VARIABLES.csv!\n")
+ self.logger.write_error(traceback.format_exc())
+ self.ResetIECProgramsAndVariables()
+ return False
+
+ return True
+
+ def Generate_plc_debugger(self):
+ """
+ Generate trace/debug code out of PLC variable list
+ """
+ self.GetIECProgramsAndVariables()
+
+ # prepare debug code
+ debug_code = runtime.code("plc_debug") % {
+ "programs_declarations":
+ "\n".join(["extern %(type)s %(C_path)s;"%p for p in self._ProgramList]),
+ "extern_variables_declarations":"\n".join([
+ {"PT":"extern %(type)s *%(C_path)s;",
+ "VAR":"extern %(type)s %(C_path)s;"}[v["vartype"]]%v
+ for v in self._VariablesList if v["C_path"].find('.')<0]),
+ "subscription_table_count":
+ len(self._VariablesList),
+ "variables_pointer_type_table_count":
+ len(self._VariablesList),
+ "variables_pointer_type_table_initializer":"\n".join([
+ {"PT":" variable_table[%(num)s].ptrvalue = (void*)(%(C_path)s);\n",
+ "VAR":" variable_table[%(num)s].ptrvalue = (void*)(&%(C_path)s);\n"}[v["vartype"]]%v +
+ " variable_table[%(num)s].type = %(type)s_ENUM;\n"%v
+ for v in self._VariablesList if v["type"] in DebugTypes ])}
+
+ return debug_code
+
+ def RegisterDebugVarToConnector(self):
+ Idxs = []
+ if self._connector is not None:
+ for IECPath,WeakCallableDict in self.IECdebug_callables:
+ if len(WeakCallableDict) == 0:
+ # Callable Dict is empty.
+ # This variable is not needed anymore!
+ self.IECdebug_callables.pop(IECPath)
+ else:
+ # Convert
+ Idx = self._IECPathToIdx.get(IECPath,None)
+ if Idx is not None:
+ Idxs.append(Idx)
+ else:
+ self.logger.write_warning("Debug : Unknown variable %s\n"%IECPath)
+ self._connector.TraceVariables(Idxs)
+
+ def SubscribeDebugIECVariable(self, IECPath, callable, *args, **kwargs):
+ """
+ Dispatching use a dictionnary linking IEC variable paths
+ to a WeakKeyDictionary linking
+ weakly referenced callables to optionnal args
+ """
+ # If no entry exist, create a new one with a fresh WeakKeyDictionary
+ self.IECdebug_callables.setdefault(
+ IECPath,
+ WeakKeyDictionary())[callable]=(args, kwargs)
+ # Rearm anti-rapid-fire timer
+ self.DebugTimer.cancel()
+ self.DebugTimer.start()
+
+ def Generate_plc_common_main(self):
+ """
+ Use plugins layout given in LocationCFilesAndCFLAGS to
+ generate glue code that dispatch calls to all plugins
+ """
+ # filter location that are related to code that will be called
+ # in retreive, publish, init, cleanup
+ locstrs = map(lambda x:"_".join(map(str,x)),
+ [loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls])
+
+ # Generate main, based on template
+ plc_main_code = runtime.code("plc_common_main") % {
+ "calls_prototypes":"\n".join([(
+ "int __init_%(s)s(int argc,char **argv);\n"+
+ "void __cleanup_%(s)s();\n"+
+ "void __retrieve_%(s)s();\n"+
+ "void __publish_%(s)s();")%{'s':locstr} for locstr in locstrs]),
+ "retrieve_calls":"\n ".join([
+ "__retrieve_%s();"%locstr for locstr in locstrs]),
+ "publish_calls":"\n ".join([ #Call publish in reverse order
+ "__publish_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
+ "init_calls":"\n ".join([
+ "init_level=%d; "%i+
+ "if(res = __init_%s(argc,argv)){"%locstr +
+ #"printf(\"%s\"); "%locstr + #for debug
+ "return res;}" for i,locstr in enumerate(locstrs)]),
+ "cleanup_calls":"\n ".join([
+ "if(init_level >= %d) "%i+
+ "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
+ }
+
+ target_name = self.BeremizRoot.getTargetType().getcontent()["name"]
+ plc_main_code += runtime.code("plc_%s_main"%target_name)
+
+ return plc_main_code
+
+
+ def _build(self):
"""
Method called by user to (re)build SoftPLC and plugin tree
"""
@@ -945,113 +1137,90 @@
# Eventually create build dir
if not os.path.exists(buildpath):
os.mkdir(buildpath)
-
- logger.flush()
- logger.write("Start build in %s\n" % buildpath)
-
+ # There is something to clean
self.EnableMethod("_Clean", True)
- self.EnableMethod("_showIECcode", True)
-
- # Generate SoftPLC code
- if not self._Generate_SoftPLC(logger):
- logger.write_error("SoftPLC code generation failed !\n")
+
+ self.logger.flush()
+ self.logger.write("Start build in %s\n" % buildpath)
+
+ # Generate SoftPLC IEC code
+ IECGenRes = self._Generate_SoftPLC()
+ self.ShowMethod("_showIECcode", True)
+
+ # If IEC code gen fail, bail out.
+ if not IECGenRes:
+ self.logger.write_error("IEC-61131-3 code generation failed !\n")
return False
-
- #logger.write("SoftPLC code generation successfull\n")
-
- logger.write("Generating plugins code ...\n")
+ # Reset variable and program list that are parsed from
+ # CSV file generated by IEC2C compiler.
+ self.ResetIECProgramsAndVariables()
# Generate C code and compilation params from plugin hierarchy
+ self.logger.write("Generating plugins C code\n")
try:
- LocationCFilesAndCFLAGS,LDFLAGS = self._Generate_C(
+ self.LocationCFilesAndCFLAGS, self.LDFLAGS, ExtraFiles = self._Generate_C(
buildpath,
- self.PLCGeneratedLocatedVars,
- logger)
+ self.PLCGeneratedLocatedVars)
except Exception, exc:
- logger.write_error("Plugins code generation Failed !\n")
- logger.write_error(traceback.format_exc())
+ self.logger.write_error("Plugins code generation failed !\n")
+ self.logger.write_error(traceback.format_exc())
return False
-
- #debug
- #import pprint
- #pp = pprint.PrettyPrinter(indent=4)
- #logger.write("LocationCFilesAndCFLAGS :\n"+pp.pformat(LocationCFilesAndCFLAGS)+"\n")
- #logger.write("LDFLAGS :\n"+pp.pformat(LDFLAGS)+"\n")
-
- # Generate main
- locstrs = map(lambda x:"_".join(map(str,x)), [loc for loc,Cfiles,DoCalls in LocationCFilesAndCFLAGS if loc and DoCalls])
- plc_main = runtime.code("plc_common_main") % {
- "calls_prototypes":"\n".join(
- ["int __init_%(s)s(int argc,char **argv);\nvoid __cleanup_%(s)s();\nvoid __retrieve_%(s)s();\nvoid __publish_%(s)s();"%
- {'s':locstr} for locstr in locstrs]),
- "retrieve_calls":"\n ".join(["__retrieve_%(s)s();"%{'s':locstr} for locstr in locstrs]),
- "publish_calls":"\n ".join(["__publish_%(s)s();"%{'s':locstr} for locstr in locstrs]),
- "init_calls":"\n ".join(["init_level++; if(res = __init_%(s)s(argc,argv)) return res;"%{'s':locstr} for locstr in locstrs]),
- "cleanup_calls":"\n ".join(["if(init_level-- > 0) __cleanup_%(s)s();"%{'s':locstr} for locstr in locstrs]),
- "sync_align_ratio":self.BeremizRoot.getSync_Align_Ratio()}
- target_name = self.BeremizRoot.TargetType.content["name"]
- plc_main += runtime.code("plc_%s_main"%target_name)
-
- main_path = os.path.join(buildpath, "main.c" )
- f = open(main_path,'w')
- f.write(plc_main)
- f.close()
- # First element is necessarely root
- LocationCFilesAndCFLAGS[0][1].insert(0,(main_path, self.CFLAGS))
-
- # Compile the resulting code into object files.
- compiler = self.BeremizRoot.getCompiler()
- _CFLAGS = self.BeremizRoot.getCFLAGS()
- linker = self.BeremizRoot.getLinker()
- _LDFLAGS = self.BeremizRoot.getLDFLAGS()
- obns = []
- objs = []
- for Location, CFilesAndCFLAGS, DoCalls in LocationCFilesAndCFLAGS:
- if Location:
- logger.write("Plugin : " + self.GetChildByIECLocation(Location).GetCurrentName() + " " + str(Location)+"\n")
- else:
- logger.write("PLC :\n")
-
- for CFile, CFLAGS in CFilesAndCFLAGS:
- bn = os.path.basename(CFile)
- obn = os.path.splitext(bn)[0]+".o"
- obns.append(obn)
- logger.write(" [CC] "+bn+" -> "+obn+"\n")
- objectfilename = os.path.splitext(CFile)[0]+".o"
-
- status, result, err_result = ProcessLogger(
- logger,
- "\"%s\" -c \"%s\" -o \"%s\" %s %s"%
- (compiler, CFile, objectfilename, _CFLAGS, CFLAGS)
- ).spin()
-
- if status != 0:
- logger.write_error("Build failed\n")
- return False
- objs.append(objectfilename)
- # Link all the object files into one executable
- logger.write("Linking :\n")
- exe = self.GetProjectName()
- if target_name == "Win32":
- exe += ".exe"
- exe_path = os.path.join(buildpath, exe)
- logger.write(" [CC] " + ' '.join(obns)+" -> " + exe + "\n")
- status, result, err_result = ProcessLogger(
- logger,
- "\"%s\" \"%s\" -o \"%s\" %s"%
- (linker,
- '" "'.join(objs),
- exe_path,
- ' '.join(LDFLAGS+[_LDFLAGS]))
- ).spin()
- if status != 0:
- logger.write_error("Build failed\n")
- self.EnableMethod("_Run", False)
+ # Get temprary directory path
+ extrafilespath = self._getExtraFilesPath()
+ # Remove old directory
+ if os.path.exists(extrafilespath):
+ shutil.rmtree(extrafilespath)
+ # Recreate directory
+ os.mkdir(extrafilespath)
+ # Then write the files
+ for fname,fobject in ExtraFiles:
+ print fname,fobject
+ fpath = os.path.join(extrafilespath,fname)
+ open(fpath, "wb").write(fobject.read())
+ # Now we can forget ExtraFiles (will close files object)
+ del ExtraFiles
+
+ # Template based part of C code generation
+ # files are stacked at the beginning, as files of plugin tree root
+ for generator, filename, name in [
+ # debugger code
+ (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
+ # init/cleanup/retrieve/publish, run and align code
+ (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]:
+ try:
+ # Do generate
+ code = generator()
+ code_path = os.path.join(buildpath,filename)
+ open(code_path, "w").write(code)
+ # Insert this file as first file to be compiled at root plugin
+ self.LocationCFilesAndCFLAGS[0][1].insert(0,(code_path, self.plcCFLAGS))
+ except Exception, exc:
+ self.logger.write_error(name+" generation failed !\n")
+ self.logger.write_error(traceback.format_exc())
+ return False
+
+ self.logger.write("C code generated successfully.\n")
+
+ # Get current or fresh builder
+ builder = self.GetBuilder()
+ if builder is None:
+ self.logger.write_error("Fatal : cannot get builder.\n")
return False
-
- self.EnableMethod("_Run", True)
+
+ # Build
+ try:
+ if not builder.build() :
+ self.logger.write_error("C Build failed.\n")
+ return False
+ except Exception, exc:
+ self.logger.write_error("C Build crashed !\n")
+ self.logger.write_error(traceback.format_exc())
+ return False
+
+ # Update GUI status about need for transfer
+ self.CompareLocalAndRemotePLC()
return True
def ShowError(self, logger, from_location, to_location):
@@ -1061,8 +1230,8 @@
start = (from_location[0] - start_row, from_location[1] - start_col)
end = (to_location[0] - start_row, to_location[1] - start_col)
self.PLCEditor.ShowError(infos, start, end)
-
- def _showIECcode(self, logger):
+
+ def _showIECcode(self):
plc_file = self._getIECcodepath()
new_dialog = wx.Frame(self.AppFrame)
ST_viewer = TextViewer(new_dialog, "", None, None)
@@ -1076,14 +1245,9 @@
new_dialog.Show()
- def _editIECrawcode(self, logger):
+ def _editIECrawcode(self):
new_dialog = wx.Frame(self.AppFrame)
- buildpath = self._getBuildPath()
- # Eventually create build dir
- if not os.path.exists(buildpath):
- os.mkdir(buildpath)
-
controler = MiniTextControler(self._getIECrawcodepath())
ST_viewer = TextViewer(new_dialog, "", None, controler)
#ST_viewer.Enable(False)
@@ -1092,7 +1256,7 @@
new_dialog.Show()
- def _EditPLC(self, logger):
+ def _EditPLC(self):
if self.PLCEditor is None:
self.RefreshPluginsBlockLists()
def _onclose():
@@ -1108,83 +1272,245 @@
self.PLCEditor._onsave = _onsave
self.PLCEditor.Show()
- def _Clean(self, logger):
+ def _Clean(self):
if os.path.isdir(os.path.join(self._getBuildPath())):
- logger.write("Cleaning the build directory\n")
+ self.logger.write("Cleaning the build directory\n")
shutil.rmtree(os.path.join(self._getBuildPath()))
else:
- logger.write_error("Build directory already clean\n")
- self.EnableMethod("_showIECcode", False)
+ self.logger.write_error("Build directory already clean\n")
+ self.ShowMethod("_showIECcode", False)
self.EnableMethod("_Clean", False)
- self.EnableMethod("_Run", False)
-
- def _Run(self, logger):
- command_start_plc = os.path.join(self._getBuildPath(),self.GetProjectName() + exe_ext)
- if os.path.isfile(command_start_plc):
- has_gui_plugin = False
- for PlugChild in self.IterChilds():
- has_gui_plugin |= PlugChild.IsGUIPlugin()
- logger.write("Starting PLC\n")
- def this_plc_finish_callback(*args):
- if self.runningPLC is not None:
- self.runningPLC = None
- self.reset_finished()
- self.runningPLC = ProcessLogger(
- logger,
- command_start_plc,
- finish_callback = this_plc_finish_callback,
- no_gui=wx.Platform != '__WXMSW__' or not has_gui_plugin)
- self.EnableMethod("_Clean", False)
- self.EnableMethod("_Run", False)
- self.EnableMethod("_Stop", True)
- self.EnableMethod("_build", False)
+ self.CompareLocalAndRemotePLC()
+
+ ############# Real PLC object access #############
+ def UpdateMethodsFromPLCStatus(self):
+ # Get PLC state : Running or Stopped
+ # TODO : use explicit status instead of boolean
+ if self._connector is not None:
+ status = self._connector.GetPLCstatus()
+ self.logger.write("PLC is %s\n"%status)
else:
- logger.write_error("%s doesn't exist\n" %command_start_plc)
-
- def reset_finished(self):
- self.EnableMethod("_Clean", True)
- self.EnableMethod("_Run", True)
- self.EnableMethod("_Stop", False)
- self.EnableMethod("_build", True)
-
- def _Stop(self, logger):
- if self.runningPLC is not None:
- logger.write("Stopping PLC\n")
- was_runningPLC = self.runningPLC
- self.runningPLC = None
- was_runningPLC.kill()
- self.reset_finished()
+ status = "Disconnected"
+ for args in {
+ "Started":[("_Run", False),
+ ("_Debug", False),
+ ("_Stop", True)],
+ "Stopped":[("_Run", True),
+ ("_Debug", True),
+ ("_Stop", False)],
+ "Empty": [("_Run", False),
+ ("_Debug", False),
+ ("_Stop", False)],
+ "Dirty": [("_Run", True),
+ ("_Debug", True),
+ ("_Stop", False)],
+ "Disconnected": [("_Run", False),
+ ("_Debug", False),
+ ("_Stop", False)],
+ }.get(status,[]):
+ self.ShowMethod(*args)
+
+ def _Run(self):
+ """
+ Start PLC
+ """
+ if self._connector.StartPLC():
+ self.logger.write("Starting PLC\n")
+ else:
+ self.logger.write_error("Couldn't start PLC !\n")
+ self.UpdateMethodsFromPLCStatus()
+
+ def _Debug(self):
+ """
+ Start PLC (Debug Mode)
+ """
+ if self.GetIECProgramsAndVariables() and self._connector.StartPLC():
+ self.logger.write("Starting PLC (debug mode)\n")
+ # TODO : laucnch PLCOpenEditor in Debug Mode
+ self.logger.write_warning("Debug mode for PLCopenEditor not implemented\n")
+ self.logger.write_warning("Starting alternative test GUI\n")
+ # TODO : laucnch PLCOpenEditor in Debug Mode
+ else:
+ self.logger.write_error("Couldn't start PLC debug !\n")
+ self.UpdateMethodsFromPLCStatus()
+
+ def _Stop(self):
+ """
+ Stop PLC
+ """
+ if self._connector.StopPLC():
+ self.logger.write("Stopping PLC\n")
+ else:
+ self.logger.write_error("Couldn't stop PLC !\n")
+ self.UpdateMethodsFromPLCStatus()
+
+ def _Connect(self):
+ # don't accept re-connetion is already connected
+ if self._connector is not None:
+ self.logger.write_error("Already connected. Please disconnect\n")
+ return
+
+ # Get connector uri
+ uri = self.\
+ BeremizRoot.\
+ getTargetType().\
+ getcontent()["value"].\
+ getConnection().\
+ getURI_location().\
+ strip()
+
+ # if uri is empty launch discovery dialog
+ if uri == "":
+ # Launch Service Discovery dialog
+ dia = DiscoveryDialog(self.AppFrame)
+ dia.ShowModal()
+ uri = dia.GetResult()
+ # Nothing choosed or cancel button
+ if uri is None:
+ return
+ else:
+ self.\
+ BeremizRoot.\
+ getTargetType().\
+ getcontent()["value"].\
+ getConnection().\
+ setURI_location(uri)
+
+ # Get connector from uri
+ try:
+ self._connector = connectors.ConnectorFactory(uri, self)
+ except Exception, msg:
+ self.logger.write_error("Exception while connecting %s!\n"%uri)
+ self.logger.write_error(traceback.format_exc())
+
+ # Did connection success ?
+ if self._connector is None:
+ # Oups.
+ self.logger.write_error("Connection failed to %s!\n"%uri)
+ else:
+ self.ShowMethod("_Connect", False)
+ self.ShowMethod("_Disconnect", True)
+ self.ShowMethod("_Transfer", True)
+
+ self.CompareLocalAndRemotePLC()
+ self.UpdateMethodsFromPLCStatus()
+
+ def CompareLocalAndRemotePLC(self):
+ if self._connector is None:
+ return
+ # We are now connected. Update button status
+ MD5 = self.GetLastBuildMD5()
+ # Check remote target PLC correspondance to that md5
+ if MD5 is not None:
+ if not self._connector.MatchMD5(MD5):
+ self.logger.write_warning(
+ "Latest build do not match with target, please transfer.\n")
+ self.EnableMethod("_Transfer", True)
+ else:
+ self.logger.write(
+ "Latest build match target, no transfer needed.\n")
+ self.EnableMethod("_Transfer", True)
+ #self.EnableMethod("_Transfer", False)
+ else:
+ self.logger.write_warning(
+ "Cannot compare latest build to target. Please build.\n")
+ self.EnableMethod("_Transfer", False)
+
+
+ def _Disconnect(self):
+ self._connector = None
+ self.ShowMethod("_Transfer", False)
+ self.ShowMethod("_Connect", True)
+ self.ShowMethod("_Disconnect", False)
+ self.UpdateMethodsFromPLCStatus()
+
+ def _Transfer(self):
+ # Get the last build PLC's
+ MD5 = self.GetLastBuildMD5()
+
+ # Check if md5 file is empty : ask user to build PLC
+ if MD5 is None :
+ self.logger.write_error("Failed : Must build before transfer.\n")
+ return False
+
+ # Compare PLC project with PLC on target
+ if self._connector.MatchMD5(MD5):
+ self.logger.write(
+ "Latest build already match current target. Transfering anyway...\n")
+
+ # Get temprary directory path
+ extrafilespath = self._getExtraFilesPath()
+ extrafiles = [(name, open(os.path.join(extrafilespath, name),
+ 'rb').read()) \
+ for name in os.listdir(extrafilespath) \
+ if not name=="CVS"]
+
+ for filename, unused in extrafiles:
+ print filename
+
+ # Send PLC on target
+ builder = self.GetBuilder()
+ if builder is not None:
+ data = builder.GetBinaryCode()
+ if data is not None :
+ if self._connector.NewPLC(MD5, data, extrafiles):
+ self.logger.write("Transfer completed successfully.\n")
+ else:
+ self.logger.write_error("Transfer failed\n")
+ else:
+ self.logger.write_error("No PLC to transfer (did build success ?)\n")
+ self.UpdateMethodsFromPLCStatus()
PluginMethods = [
- {"bitmap" : os.path.join("images", "editPLC"),
+ {"bitmap" : opjimg("editPLC"),
"name" : "Edit PLC",
"tooltip" : "Edit PLC program with PLCOpenEditor",
"method" : "_EditPLC"},
- {"bitmap" : os.path.join("images", "Build"),
+ {"bitmap" : opjimg("Build"),
"name" : "Build",
"tooltip" : "Build project into build folder",
"method" : "_build"},
- {"bitmap" : os.path.join("images", "Clean"),
+ {"bitmap" : opjimg("Clean"),
"name" : "Clean",
+ "enabled" : False,
"tooltip" : "Clean project build folder",
"method" : "_Clean"},
- {"bitmap" : os.path.join("images", "Run"),
+ {"bitmap" : opjimg("Run"),
"name" : "Run",
- "enabled" : False,
- "tooltip" : "Run PLC from build folder",
+ "shown" : False,
+ "tooltip" : "Start PLC",
"method" : "_Run"},
- {"bitmap" : os.path.join("images", "Stop"),
+ {"bitmap" : opjimg("Debug"),
+ "name" : "Debug",
+ "shown" : False,
+ "tooltip" : "Start PLC (debug mode)",
+ "method" : "_Debug"},
+ {"bitmap" : opjimg("Stop"),
"name" : "Stop",
- "enabled" : False,
+ "shown" : False,
"tooltip" : "Stop Running PLC",
"method" : "_Stop"},
- {"bitmap" : os.path.join("images", "ShowIECcode"),
+ {"bitmap" : opjimg("Connect"),
+ "name" : "Connect",
+ "tooltip" : "Connect to the target PLC",
+ "method" : "_Connect"},
+ {"bitmap" : opjimg("Transfer"),
+ "name" : "Transfer",
+ "shown" : False,
+ "tooltip" : "Transfer PLC",
+ "method" : "_Transfer"},
+ {"bitmap" : opjimg("Disconnect"),
+ "name" : "Disconnect",
+ "shown" : False,
+ "tooltip" : "Disconnect from PLC",
+ "method" : "_Disconnect"},
+ {"bitmap" : opjimg("ShowIECcode"),
"name" : "Show code",
- "enabled" : False,
+ "shown" : False,
"tooltip" : "Show IEC code generated by PLCGenerator",
"method" : "_showIECcode"},
- {"bitmap" : os.path.join("images", "editIECrawcode"),
+ {"bitmap" : opjimg("editIECrawcode"),
"name" : "Append code",
"tooltip" : "Edit raw IEC code added to code generated by PLCGenerator",
- "method" : "_editIECrawcode"}
+ "method" : "_editIECrawcode"},
]
--- a/plugins/c_ext/c_ext.py Tue Aug 12 16:27:07 2008 +0200
+++ b/plugins/c_ext/c_ext.py Wed Aug 20 00:11:40 2008 +0200
@@ -188,7 +188,7 @@
return ""
_View = None
- def _OpenView(self, logger):
+ def _OpenView(self):
if not self._View:
def _onclose():
self._View = None
@@ -222,7 +222,7 @@
self.CFileBuffer.CurrentSaved()
return True
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
"""
Generate C code
@param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
@@ -359,7 +359,7 @@
PlugChildsTypes = [("C_File",_Cfile, "C file")]
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
return [],"",False
--- a/plugins/canfestival/canfestival.py Tue Aug 12 16:27:07 2008 +0200
+++ b/plugins/canfestival/canfestival.py Wed Aug 20 00:11:40 2008 +0200
@@ -30,6 +30,14 @@
<xsd:attribute name="CAN_Baudrate" type="xsd:string" use="required"/>
<xsd:attribute name="NodeId" type="xsd:string" use="required"/>
<xsd:attribute name="Sync_Align" type="xsd:integer" use="optional" default="0"/>
+ <xsd:attribute name="Sync_Align_Ratio" use="optional" default="50">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="99"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -73,7 +81,7 @@
[]) # options
dialog.Destroy()
_View = None
- def _OpenView(self, logger):
+ def _OpenView(self):
if not self._View:
def _onclose():
self._View = None
@@ -103,7 +111,7 @@
def OnPlugSave(self):
return self.SaveCurrentInFile(self.GetSlaveODPath())
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
"""
Generate C code
@param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
@@ -158,7 +166,7 @@
self.LoadProject(self.PlugPath())
_View = None
- def _OpenView(self, logger):
+ def _OpenView(self):
if not self._View:
def _onclose():
self._View = None
@@ -171,16 +179,16 @@
self._View._onsave = _onsave
self._View.Show()
- def _ShowMasterGenerated(self, logger):
+ def _ShowMasterGenerated(self):
buildpath = self._getBuildPath()
# Eventually create build dir
if not os.path.exists(buildpath):
- logger.write_error("Error: No PLC built\n")
+ self.logger.write_error("Error: No PLC built\n")
return
masterpath = os.path.join(buildpath, "MasterGenerated.od")
if not os.path.exists(masterpath):
- logger.write_error("Error: No Master generated\n")
+ self.logger.write_error("Error: No Master generated\n")
return
new_dialog = objdictedit(None, filesOpen=[masterpath])
@@ -207,7 +215,7 @@
self.SetRoot(self.PlugPath())
return self.SaveProject() is not None
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
"""
Generate C code
@param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
@@ -264,7 +272,7 @@
return infos
return infos
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
format_dict = {"locstr" : "_".join(map(str,self.GetCurrentLocation())),
"candriver" : self.CanFestivalInstance.getCAN_Driver(),
@@ -328,15 +336,16 @@
else:
# Slave node
align = child_data.getSync_Align()
+ align_ratio=child_data.getSync_Align_Ratio()
if align > 0:
format_dict["post_sync"] += (
"static int %s_CalCount = 0;\n"%(nodename)+
"static void %s_post_sync(CO_Data* d){\n"%(nodename)+
" if(%s_CalCount < %d){\n"%(nodename, align)+
" %s_CalCount++;\n"%(nodename)+
- " align_tick(1);\n"+
+ " align_tick(-1);\n"+
" }else{\n"+
- " align_tick(0);\n"+
+ " align_tick(%d);\n"%(align_ratio)+
" }\n"+
"}\n")
format_dict["post_sync_register"] += (
--- a/plugins/canfestival/cf_runtime.c Tue Aug 12 16:27:07 2008 +0200
+++ b/plugins/canfestival/cf_runtime.c Wed Aug 20 00:11:40 2008 +0200
@@ -77,12 +77,11 @@
void __cleanup_%(locstr)s()
{
- %(nodes_close)s
-
// Stop timer thread
- if(init_level-- > 0)
+ if(init_level-- > 0){
StopTimerLoop(&Exit);
-
+ %(nodes_close)s
+ }
#if !defined(WIN32) || defined(__CYGWIN__)
TimerCleanup();
#endif
--- a/plugins/svgui/svgui.py Tue Aug 12 16:27:07 2008 +0200
+++ b/plugins/svgui/svgui.py Wed Aug 20 00:11:40 2008 +0200
@@ -116,9 +116,6 @@
self.CreateNewInterface()
self.SetFilePath(filepath)
- def IsGUIPlugin(self):
- return True
-
def GetElementIdFromName(self, name):
element = self.GetElementByName(name)
if element is not None:
@@ -126,7 +123,7 @@
return None
_View = None
- def _OpenView(self, logger):
+ def _OpenView(self):
if not self._View:
def _onclose():
self._View = None
@@ -137,7 +134,7 @@
self._View._onsave = _onsave
self._View.Show()
- def _ImportSVG(self, logger):
+ def _ImportSVG(self):
if not self._View:
dialog = wx.FileDialog(self.GetPlugRoot().AppFrame, "Choose a SVG file", os.getcwd(), "", "SVG files (*.svg)|*.svg|All files|*.*", wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
@@ -145,10 +142,10 @@
if os.path.isfile(svgpath):
shutil.copy(svgpath, os.path.join(self.PlugPath(), "gui.svg"))
else:
- logger.write_error("No such SVG file: %s\n"%svgpath)
+ self.logger.write_error("No such SVG file: %s\n"%svgpath)
dialog.Destroy()
- def _ImportXML(self, logger):
+ def _ImportXML(self):
if not self._View:
dialog = wx.FileDialog(self.GetPlugRoot().AppFrame, "Choose a XML file", os.getcwd(), "", "XML files (*.xml)|*.xml|All files|*.*", wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
@@ -156,7 +153,7 @@
if os.path.isfile(xmlpath):
shutil.copy(xmlpath, os.path.join(self.PlugPath(), "gui.xml"))
else:
- logger.write_error("No such XML file: %s\n"%xmlpath)
+ self.logger.write_error("No such XML file: %s\n"%xmlpath)
dialog.Destroy()
PluginMethods = [
@@ -178,7 +175,7 @@
self.SaveXMLFile(os.path.join(self.PlugPath(), "gui.xml"))
return True
- def PlugGenerate_C(self, buildpath, locations, logger):
+ def PlugGenerate_C(self, buildpath, locations):
progname = "SVGUI_%s"%"_".join(map(str, self.GetCurrentLocation()))
doc = SVGDocument(self.GetSVGFilePath())
@@ -186,10 +183,15 @@
window_size = (int(float(root_element.GetAttribute("width"))),
int(float(root_element.GetAttribute("height"))))
- svgfilepath = self.GetSVGFilePath()
- xmlfilepath = self.GetFilePath()
- shutil.copy(svgfilepath, buildpath)
- shutil.copy(xmlfilepath, buildpath)
+# svgfilepath = self.GetSVGFilePath()
+# xmlfilepath = self.GetFilePath()
+# shutil.copy(svgfilepath, buildpath)
+# shutil.copy(xmlfilepath, buildpath)
+
+ SVGFilePath = self.GetSVGFilePath()
+ SVGFileBaseName = os.path.split(SVGFilePath)[1]
+ FilePath = self.GetFilePath()
+ FileBaseName = os.path.split(FilePath)[1]
generator = _SVGUICGenerator(self, self.GetElementsByType(),
os.path.split(self.GetSVGFilePath())[1],
@@ -202,17 +204,17 @@
cxx_flags = "-I..\\..\\wxPython-src-2.8.7.1\\bld\\lib\\wx\\include\\msw-unicode-release-2.8 -I..\\..\\wxPython-src-2.8.7.1\\include -I..\\..\\wxPython-src-2.8.7.1\\contrib\\include -I..\\..\\matiec\\lib -DWXUSINGDLL -D__WXMSW__ -mthreads"
libs = "\"..\\lib\\libwxsvg.a\" \"..\\lib\\libwxsvg_agg.a\" \"..\\lib\\libagg.a\" \"..\\lib\\libaggplatformwin32.a\" \"..\\lib\\libaggfontwin32tt.a\" -L..\\..\\wxPython-src-2.8.7.1\\bld\\lib -mno-cygwin -mwindows -mthreads -mno-cygwin -mwindows -Wl,--subsystem,windows -mwindows -lwx_mswu_richtext-2.8 -lwx_mswu_aui-2.8 -lwx_mswu_xrc-2.8 -lwx_mswu_qa-2.8 -lwx_mswu_html-2.8 -lwx_mswu_adv-2.8 -lwx_mswu_core-2.8 -lwx_baseu_xml-2.8 -lwx_baseu_net-2.8 -lwx_baseu-2.8"
else:
- status, result, err_result = ProcessLogger(logger, "wx-config --cxxflags", no_stdout=True).spin()
+ status, result, err_result = ProcessLogger(self.logger, "wx-config --cxxflags", no_stdout=True).spin()
if status:
- logger.write_error("Unable to get wx cxxflags\n")
+ self.logger.write_error("Unable to get wx cxxflags\n")
cxx_flags = result.strip() + " -I../matiec/lib"
- status, result, err_result = ProcessLogger(logger, "wx-config --libs", no_stdout=True).spin()
+ status, result, err_result = ProcessLogger(self.logger, "wx-config --libs", no_stdout=True).spin()
if status:
- logger.write_error("Unable to get wx libs\n")
+ self.logger.write_error("Unable to get wx libs\n")
libs = result.strip() + " -lwxsvg"
- return [(Gen_C_file, cxx_flags)],libs,True
+ return [(Gen_C_file, cxx_flags)],libs,True,(SVGFileBaseName, file(SVGFilePath, "rb")), (FileBaseName, file(FilePath, "rb"))
def BlockTypesFactory(self):
@@ -330,7 +332,8 @@
self.Controler = controler
def GenerateProgramHeadersPublicVars(self):
- text = """ void OnPlcOutEvent(wxEvent& event);
+ text = """
+ void OnPlcOutEvent(wxEvent& event);
void Retrieve();
void Publish();
@@ -382,7 +385,6 @@
text += self.GenerateIECVars()
text += """IMPLEMENT_APP_NO_MAIN(SVGViewApp);
-IMPLEMENT_WX_THEME_SUPPORT;
SVGViewApp *myapp = NULL;
wxSemaphore MyInitSem;
@@ -412,12 +414,14 @@
THREAD_RETURN_TYPE InitWxEntry(void* args)
{
wxEntry(myargc,myargv);
+ MyInitSem.Post();
return 0;
}
"""
- text += """bool SVGViewApp::OnInit()
+ text += """
+bool SVGViewApp::OnInit()
{
#ifndef __WXMSW__
setlocale(LC_NUMERIC, "C");
@@ -448,6 +452,12 @@
void __cleanup_%(location)s()
{
+ if(myapp){
+ wxCloseEvent event(wxEVT_CLOSE_WINDOW);
+ myapp->frame->AddPendingEvent(event);
+ myapp = NULL;
+ }
+ MyInitSem.Wait();
}
void __retrieve_%(location)s()
@@ -506,9 +516,8 @@
def GenerateProgramInitFrame(self):
text = """MainFrame::MainFrame(wxWindow *parent, const wxString& title, const wxPoint& pos,const wxSize& size, long style): wxFrame(parent, wxID_ANY, title, pos, size, style)
{
- wxFileName apppath(wxTheApp->argv[0]);
- wxFileName svgfilepath(apppath.GetPath(), wxT("%s"));
- wxFileName xmlfilepath(apppath.GetPath(), wxT("%s"));
+ wxFileName svgfilepath(wxTheApp->argv[1], wxT("%s"));
+ wxFileName xmlfilepath(wxTheApp->argv[1], wxT("%s"));
m_svgCtrl = new Program(this);
if (m_svgCtrl->LoadFiles(svgfilepath.GetFullPath(), xmlfilepath.GetFullPath()))
@@ -522,8 +531,7 @@
}
else
{
- printf("Error while opening files\\n");
- exit(0);
+ printf("Error while opening SVGUI files\\n");
}
}
@@ -545,13 +553,19 @@
current_location = "_".join(map(str, self.CurrentLocation))
for element in self.Elements:
element_type = GetElementType(element)
- element_lock = """ if (COMPARE_AND_SWAP_VAL(&in_state_%d, CHANGED, GUI_BUSY) == CHANGED ||
+ element_lock = """
+ if (COMPARE_AND_SWAP_VAL(&in_state_%d, CHANGED, GUI_BUSY) == CHANGED ||
COMPARE_AND_SWAP_VAL(&in_state_%d, UNCHANGED, GUI_BUSY) == UNCHANGED) {
"""%(element.getid(), element.getid())
- element_unlock = """ COMPARE_AND_SWAP_VAL(&in_state_%d, GUI_BUSY, CHANGED);
+ element_unlock = """
+ COMPARE_AND_SWAP_VAL(&in_state_%d, GUI_BUSY, CHANGED);
+ event.Skip();
+ }else{
+ /* re post event for idle */
+ AddPendingEvent(event);
}
- else
- ProcessEvent(event);
+}
+
"""%element.getid()
element_name = element.getname()
@@ -562,7 +576,6 @@
text += element_lock
text += " _copy__IX%s_%d_1 = button->GetToggle();\n"%(current_location, element.getid())
text += element_unlock
- text += " event.Skip();\n}\n\n"
elif element_type == ITEM_ROTATING:
text += """void Program::On%sChanging(wxScrollEvent& event)
{
@@ -571,7 +584,6 @@
text += element_lock
text += " _copy__ID%s_%d_1 = rotating->GetAngle();\n"%(current_location, element.getid())
text += element_unlock
- text += " event.Skip();\n}\n\n"
elif element_type == ITEM_NOTEBOOK:
text += """void Program::On%sTabChanged(wxNotebookEvent& event)
{
@@ -580,7 +592,6 @@
text += element_lock
text += " _copy__IB%s_%d_1 = notebook->GetCurrentPage();\n"%(current_location, element.getid())
text += element_unlock
- text += " event.Skip();\n}\n\n"
elif element_type == ITEM_TRANSFORM:
text += """void Program::On%sChanging(wxScrollEvent& event)
{
@@ -590,7 +601,6 @@
text += " _copy__ID%s_%d_1 = transform->GetX();\n"%(current_location, element.getid())
text += " _copy__ID%s_%d_2 = transform->GetY();\n"%(current_location, element.getid())
text += element_unlock
- text += " event.Skip();\n}\n\n"
text += "/* OnPlcOutEvent update GUI with provided IEC __Q* PLC output variables */\n"
text += """void Program::OnPlcOutEvent(wxEvent& event)
@@ -599,7 +609,7 @@
refreshing = true;
- wxMutexGuiEnter();
+
"""
for element in self.Elements:
element_type = GetElementType(element)
@@ -659,7 +669,7 @@
"""%texts
text += " COMPARE_AND_SWAP_VAL(&out_state_%(id)d, GUI_BUSY, UNCHANGED);\n }\n"%texts
- text += """ wxMutexGuiLeave();
+ text += """
refreshing = false;
@@ -716,7 +726,7 @@
text += """ /* Replace this with determinist signal if called from RT */
if (refresh && !refreshing) {
wxCommandEvent event( EVT_PLC );
- ProcessEvent(event);
+ AddPendingEvent(event);
refresh = false;
}
};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/PLCObject.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,258 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#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 Pyro.core as pyro
+from threading import Timer
+import ctypes, os, dl, commands
+#, sys
+#sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL)
+
+if os.name == ("nt", "ce"):
+ from _ctypes import LoadLibrary as dlopen
+ from _ctypes import FreeLibrary as dlclose
+elif os.name == "posix":
+ from _ctypes import dlopen, dlclose
+
+import os,sys,traceback
+
+lib_ext ={
+ "linux2":".so",
+ "win32":".dll",
+ }.get(sys.platform, "")
+
+class PLCObject(pyro.ObjBase):
+ def __init__(self, workingdir, daemon):
+ pyro.ObjBase.__init__(self)
+ self.workingdir = workingdir
+ self.PLCStatus = "Stopped"
+ self.PLClibraryHandle = None
+ # Creates fake C funcs proxies
+ self._FreePLC()
+ self.daemon = daemon
+
+ # Get the last transfered PLC if connector must be restart
+ try:
+ self.CurrentPLCFilename=open(
+ self._GetMD5FileName(),
+ "r").read().strip() + lib_ext
+ except Exception, e:
+ self.PLCStatus = "Empty"
+ self.CurrentPLCFilename=None
+
+ def _GetMD5FileName(self):
+ return os.path.join(self.workingdir, "lasttransferedPLC.md5")
+
+ def _GetLibFileName(self):
+ return os.path.join(self.workingdir,self.CurrentPLCFilename)
+
+
+ def _LoadNewPLC(self):
+ """
+ Load PLC library
+ Declare all functions, arguments and return values
+ """
+ print "Load PLC"
+ try:
+ self._PLClibraryHandle = dlopen(self._GetLibFileName())
+ self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
+
+ self._startPLC = self.PLClibraryHandle.startPLC
+ self._startPLC.restype = ctypes.c_int
+ self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
+
+ self._stopPLC = self.PLClibraryHandle.stopPLC
+ self._stopPLC.restype = None
+
+ self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
+ self._ResetDebugVariables.restype = None
+
+ self._RegisterDebugVariable = self.PLClibraryHandle.ResetDebugVariables
+ self._RegisterDebugVariable.restype = None
+
+ self._IterDebugData = self.PLClibraryHandle.IterDebugData
+ self._IterDebugData.restype = ctypes.c_void_p
+ self._IterDebugData.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_char_p)]
+
+ self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
+ self._FreeDebugData.restype = None
+ return True
+ except:
+ print traceback.format_exc()
+ return False
+
+ def _FreePLC(self):
+ """
+ Unload PLC library.
+ This is also called by __init__ to create dummy C func proxies
+ """
+ # Forget all refs to library
+ self._startPLC = lambda:None
+ self._stopPLC = lambda:None
+ self._ResetDebugVariables = lambda:None
+ self._RegisterDebugVariable = lambda x:None
+ self._IterDebugData = lambda x,y:None
+ self._FreeDebugData = lambda:None
+ self.PLClibraryHandle = None
+ # Unload library explicitely
+ if getattr(self,"_PLClibraryHandle",None) is not None:
+ print "Unload PLC"
+ dlclose(self._PLClibraryHandle)
+ res = self._DetectDirtyLibs()
+ else:
+ res = False
+
+ self._PLClibraryHandle = None
+
+ return res
+
+ def _DetectDirtyLibs(self):
+ # Detect dirty libs
+ # Get lib dependencies (for dirty lib detection)
+ if os.name == "posix":
+ # parasiting libs listed with ldd
+ badlibs = [ toks.split()[0] for toks in commands.getoutput(
+ "ldd "+self._GetLibFileName()).splitlines() ]
+ for badlib in badlibs:
+ if badlib[:6] in ["libwx_",
+ "libwxs",
+ "libgtk",
+ "libgdk",
+ "libatk",
+ "libpan",
+ "libX11",
+ ]:
+ badhandle = dlopen(badlib, dl.RTLD_NOLOAD)
+ print "Dirty lib detected :" + badlib
+ #dlclose(badhandle)
+ return True
+ return False
+
+
+ def StartPLC(self):
+ print "StartPLC"
+ if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
+ c_argv = ctypes.c_char_p * len(sys.argv)
+ if self._LoadNewPLC() and self._startPLC(len(sys.argv),c_argv(*sys.argv)) == 0:
+ self.PLCStatus = "Started"
+ return True
+ else:
+ print "_StartPLC did not return 0 !"
+ return False
+
+ def StopPLC(self):
+ if self.PLCStatus == "Started":
+ self._stopPLC()
+ self.PLCStatus = "Stopped"
+ if self._FreePLC():
+ self.PLCStatus = "Dirty"
+ return True
+ return False
+
+ def _Reload(self):
+ self.daemon.shutdown(True)
+ self.daemon.sock.close()
+ os.execv(sys.executable,[sys.executable]+sys.argv[:])
+ # never reached
+ return 0
+
+ def ForceReload(self):
+ # respawn python interpreter
+ Timer(0.1,self._Reload).start()
+ return True
+
+ def GetPLCstatus(self):
+ return self.PLCStatus
+
+ def NewPLC(self, md5sum, data, extrafiles):
+ print "NewPLC (%s)"%md5sum
+ if self.PLCStatus in ["Stopped", "Empty", "Dirty"]:
+ NewFileName = md5sum + lib_ext
+ extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
+ try:
+ os.remove(os.path.join(self.workingdir,
+ self.CurrentPLCFilename))
+ for filename in file(extra_files_log, "r").readlines() + extra_files_log:
+ try:
+ os.remove(os.path.join(self.workingdir, filename))
+ except:
+ pass
+ except:
+ pass
+
+ try:
+ # Create new PLC file
+ open(os.path.join(self.workingdir,NewFileName),
+ 'wb').write(data)
+
+ # Store new PLC filename based on md5 key
+ open(self._GetMD5FileName(), "w").write(md5sum)
+
+ # Then write the files
+ log = file(extra_files_log, "w")
+ for fname,fdata in extrafiles:
+ fpath = os.path.join(self.workingdir,fname)
+ open(fpath, "wb").write(fdata)
+ log.write(fname+'\n')
+
+ # Store new PLC filename
+ self.CurrentPLCFilename = NewFileName
+ except:
+ print traceback.format_exc()
+ return False
+ if self.PLCStatus == "Empty":
+ self.PLCStatus = "Stopped"
+ return True
+ return False
+
+ def MatchMD5(self, MD5):
+ try:
+ last_md5 = open(self._GetMD5FileName(), "r").read()
+ return last_md5 == MD5
+ except:
+ return False
+
+ def SetTraceVariablesList(self, idxs):
+ """
+ Call ctype imported function to append
+ these indexes to registred variables in PLC debugger
+ """
+ # keep a copy of requested idx
+ self._Idxs = idxs[:]
+ self._ResetDebugVariables()
+ for idx in idxs:
+ self._RegisterDebugVariable(idx)
+
+ def GetTraceVariables(self):
+ """
+ Return a list of variables, corresponding to the list of requiered idx
+ """
+ self._WaitDebugData()
+
+ for idx in self._Idxs:
+ buffer=self._IterDebugData()
+ self._FreeDebugData()
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/ServicePublisher.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#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 Zeroconf
+
+class PublishService():
+ def __init__(self):
+ self.server = Zeroconf.Zeroconf()
+
+ def ConfigureService(self, type, name, address, port, description):
+ self.newservice = Zeroconf.ServiceInfo(type,
+ name,
+ address,
+ port,
+ weight = 0, # weight: weight of the service
+ priority= 0, # priority: priority of the service
+ properties = description)
+
+ def PublishService(self):
+ self.server.registerService(self.newservice)
\ No newline at end of file
--- a/runtime/__init__.py Tue Aug 12 16:27:07 2008 +0200
+++ b/runtime/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -8,3 +8,6 @@
return open(filename).read()
else:
return "#error %s target not implemented !!!\n"%name
+
+from PLCObject import PLCObject
+import ServicePublisher
--- a/runtime/plc_Linux_main.c Tue Aug 12 16:27:07 2008 +0200
+++ b/runtime/plc_Linux_main.c Wed Aug 20 00:11:40 2008 +0200
@@ -3,6 +3,17 @@
#include <time.h>
#include <signal.h>
#include <stdlib.h>
+#include <pthread.h>
+
+long AtomicCompareExchange(long* atomicvar,long exchange, long compared)
+{
+ return __sync_val_compare_and_swap(atomicvar, compared, exchange);
+}
+
+//long AtomicExchange(long* atomicvar,long exchange)
+//{
+// return __sync_lock_test_and_set(atomicvar, exchange);
+//}
void PLC_GetTime(IEC_TIME *CURRENT_TIME)
{
@@ -41,15 +52,16 @@
}
timer_settime (PLC_timer, 0, &timerValues, NULL);
}
-
+//
void catch_signal(int sig)
{
- signal(SIGTERM, catch_signal);
+// signal(SIGTERM, catch_signal);
signal(SIGINT, catch_signal);
printf("Got Signal %d\n",sig);
+ exit(0);
}
-int main(int argc,char **argv)
+int startPLC(int argc,char **argv)
{
struct sigevent sigev;
/* Translate PLC's microseconds to Ttick nanoseconds */
@@ -66,15 +78,35 @@
PLC_SetTimer(Ttick,Ttick);
/* install signal handler for manual break */
- signal(SIGTERM, catch_signal);
+// signal(SIGTERM, catch_signal);
signal(SIGINT, catch_signal);
- /* Wait some signal */
- pause();
- /* Stop the PLC */
- PLC_SetTimer(0,0);
+ }else{
+ return 1;
}
- __cleanup();
- timer_delete (PLC_timer);
-
return 0;
}
+
+int stopPLC()
+{
+ /* Stop the PLC */
+ PLC_SetTimer(0,0);
+ timer_delete (PLC_timer);
+ __cleanup();
+}
+
+pthread_mutex_t DebugLock = PTHREAD_MUTEX_INITIALIZER;
+
+/* from plc_debugger.c */
+void WaitDebugData()
+{
+ /* Wait signal from PLC thread */
+ pthread_mutex_lock(&DebugLock);
+}
+
+/* Called by PLC thread when debug_publish finished
+ * This is supposed to unlock debugger thread in WaitDebugData*/
+void InitiateDebugTransfer()
+{
+ /* signal debugger thread to continue*/
+ pthread_mutex_unlock(&DebugLock);
+}
--- a/runtime/plc_Win32_main.c Tue Aug 12 16:27:07 2008 +0200
+++ b/runtime/plc_Win32_main.c Wed Aug 20 00:11:40 2008 +0200
@@ -3,7 +3,15 @@
#include <time.h>
#include <windows.h>
-int localcount = 0;
+long AtomicCompareExchange(long* atomicvar,long exchange, long compared)
+{
+ return InterlockedCompareExchange(atomicvar, exchange, compared);
+}
+
+//long AtomicExchange(long* atomicvar,long exchange)
+//{
+// return InterlockedExchange(atomicvar, exchange);
+//}
struct _timeb timetmp;
void PLC_GetTime(IEC_TIME *CURRENT_TIME)
--- a/runtime/plc_common_main.c Tue Aug 12 16:27:07 2008 +0200
+++ b/runtime/plc_common_main.c Wed Aug 20 00:11:40 2008 +0200
@@ -17,6 +17,7 @@
#define maxval(a,b) ((a>b)?a:b)
#include "iec_types.h"
+/*#include "stdio.h" /* For debug */
/*
* Functions and variables provied by generated C softPLC
@@ -25,13 +26,14 @@
void config_init__(void);
/*
- * Functions and variables to export to generated C softPLC
+ * Functions and variables to export to generated C softPLC and plugins
**/
IEC_TIME __CURRENT_TIME;
+int __tick = 0;
-static int tick = 0;
-static int init_level=0;
+static int init_level = 0;
+static int Debugging = 1;
/*
* Prototypes of functions exported by plugins
@@ -44,13 +46,16 @@
void __run()
{
%(retrieve_calls)s
+
+ if(Debugging) __retrieve_debug();
- /*
- printf("run tick = %%d\n", tick + 1);
- */
- config_run__(tick++);
+ config_run__(__tick);
+
+ if(Debugging) __publish_debug();
%(publish_calls)s
+
+ __tick++;
}
/*
@@ -88,13 +93,14 @@
#define mod %%
/*
* Call this on each external sync,
+ * @param sync_align_ratio 0->100 : align ratio, < 0 : no align, calibrate period
**/
-void align_tick(int calibrate)
+void align_tick(int sync_align_ratio)
{
/*
printf("align_tick(%%d)\n", calibrate);
*/
- if(calibrate){
+ if(sync_align_ratio < 0){ /* Calibration */
if(calibration_count == CALIBRATED)
/* Re-calibration*/
calibration_count = NOT_CALIBRATED;
@@ -102,7 +108,7 @@
/* Calibration start, get time*/
PLC_GetTime(&cal_begin);
calibration_count++;
- }else{
+ }else{ /* do alignment (if possible) */
if(calibration_count >= 0){
/* End of calibration */
/* Get final time */
@@ -135,7 +141,7 @@
PLC_GetTime(&now);
elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
if(Nticks > 0){
- PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*%(sync_align_ratio)d/100; /* to be divided by Nticks */
+ PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
if(Nticks < 2){
/* When Sync source period is near Tick time */
@@ -144,9 +150,9 @@
}else{
PeriodicTcorr = Tcorr;
}
- }else if(tick > last_tick){
- last_tick = tick;
- PhaseCorr = elapsed - (Tsync*%(sync_align_ratio)d/100);
+ }else if(__tick > last_tick){
+ last_tick = __tick;
+ PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
}else{
/*PLC did not run meanwhile. Nothing to do*/
@@ -157,3 +163,15 @@
}
}
}
+
+int suspendDebug()
+{
+ /* Prevent PLC to enter debug code */
+ Debugging = 0;
+}
+
+int resumeDebug()
+{
+ /* Let PLC enter debug code */
+ Debugging = 1;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/plc_debug.c Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,151 @@
+/*
+ * DEBUGGER code
+ *
+ * On "publish", when buffer is free, debugger stores arbitrary variables
+ * content into, and mark this buffer as filled
+ *
+ *
+ * Buffer content is read asynchronously, (from non real time part),
+ * and then buffer marked free again.
+ *
+ *
+ * */
+#include "iec_types_all.h"
+#include "POUS.h"
+/*for memcpy*/
+#include <string.h>
+
+#define BUFFER_SIZE 1024
+#define MAX_SUBSCRIBTION %(subscription_table_count)d
+
+/* Atomically accessed variable for buffer state */
+#define BUFFER_FREE 0
+#define BUFFER_BUSY 1
+static long buffer_state = BUFFER_FREE;
+
+/* The buffer itself */
+char debug_buffer[BUFFER_SIZE];
+
+/* Buffer's cursor*/
+static char* buffer_cursor = debug_buffer;
+
+typedef struct{
+ void* ptrvalue;
+ __IEC_types_enum type;
+}struct_plcvar;
+
+/***
+ * Declare programs
+ **/
+%(programs_declarations)s
+
+/***
+ * Declare global variables from resources and conf
+ **/
+%(extern_variables_declarations)s
+
+static int subscription_table[MAX_SUBSCRIBTION];
+static int* latest_subscription = subscription_table;
+static int* subscription_cursor = subscription_table;
+
+struct_plcvar variable_table[%(variables_pointer_type_table_count)d];
+
+void __init_debug()
+{
+%(variables_pointer_type_table_initializer)s
+};
+
+void __cleanup_debug()
+{
+}
+
+void __retrieve_debug()
+{
+}
+
+void __publish_debug()
+{
+ /* Lock buffer */
+ long latest_state = AtomicCompareExchange(
+ &buffer_state,
+ BUFFER_FREE,
+ BUFFER_BUSY);
+
+ /* If buffer was free */
+ if(latest_state == BUFFER_FREE)
+ {
+ int* subscription;
+
+ /* Reset buffer cursor */
+ buffer_cursor = debug_buffer;
+
+ /* iterate over subscriptions */
+ for(subscription=subscription_table;
+ subscription < latest_subscription;
+ subscription++)
+ {
+ /* get variable descriptor */
+ struct_plcvar* my_var = &variable_table[*subscription];
+ char* next_cursor;
+ /* get variable size*/
+ USINT size = __get_type_enum_size(my_var->type);
+ /* compute next cursor positon*/
+ next_cursor = buffer_cursor + size;
+ /* if buffer not full */
+ if(next_cursor < debug_buffer + BUFFER_SIZE)
+ {
+ /* copy data to the buffer */
+ memcpy(buffer_cursor, my_var->ptrvalue, size);
+ /* increment cursor according size*/
+ buffer_cursor = next_cursor;
+ }else{
+ /*TODO : signal overflow*/
+ }
+ }
+
+ /* Reset buffer cursor again (for IterDebugData)*/
+ buffer_cursor = debug_buffer;
+ subscription_cursor = subscription_table;
+
+ /* Trigger asynchronous transmission (returns immediately) */
+ InitiateDebugTransfer(); /* size */
+ }
+}
+
+void RegisterDebugVariable(int idx)
+{
+ /*If subscription table not full */
+ if(latest_subscription - subscription_table < MAX_SUBSCRIBTION)
+ {
+ *(latest_subscription++) = idx;
+ /* TODO pre-calc buffer size and signal overflow*/
+ }else{
+ /*TODO : signal subscription overflow*/
+ }
+}
+
+void ResetDebugVariables(void)
+{
+ latest_subscription = subscription_table;
+}
+
+void FreeDebugData()
+{
+ /* atomically mark buffer as free */
+ long latest_state = AtomicCompareExchange(
+ &buffer_state,
+ BUFFER_BUSY,
+ BUFFER_FREE);
+}
+
+void* IterDebugData(int* idx, const char **type_name)
+{
+ if(subscription_cursor < latest_subscription){
+ *idx = *subscription_cursor;
+ struct_plcvar* my_var = &variable_table[*subscription_cursor++];
+ *type_name = __get_type_enum_name(my_var->type);
+ return my_var->ptrvalue;
+ }
+ return NULL;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Linux/XSD Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,13 @@
+
+ <xsd:element name="Linux">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Connection">
+ <xsd:complexType>
+ <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ %(toolchain_gcc)s
+ </xsd:complexType>
+ </xsd:element>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Linux/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,5 @@
+from .. import toolchain_gcc
+
+class Linux_target(toolchain_gcc):
+ extension = ".so"
+ CustomLDFLAGS = ["-shared"]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Rtai/XSD Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,14 @@
+
+ <xsd:element name="Rtai">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Connection">
+ <xsd:complexType>
+ <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ %(toolchain_gcc)s
+ <xsd:attribute name="rtai_config" type="xsd:string" use="optional" default="/usr/realtime/"/>
+ </xsd:complexType>
+ </xsd:element>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Rtai/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,1 @@
+from target_rtai import *
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Rtai/target_rtai.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,3 @@
+class rtai_target(targets.target_gcc):
+ extensionexe = "exe"
+ extensiondll = "dll"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Win32/XSD Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,14 @@
+
+ <xsd:element name="Win32">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Connection">
+ <xsd:complexType>
+ <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ %(toolchain_gcc)s
+ </xsd:complexType>
+ </xsd:element>
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Win32/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,10 @@
+from .. import toolchain_gcc
+
+class Win32_target(toolchain_gcc):
+ extension = ".dll"
+ CustomLDFLAGS = ["-shared",
+ "-Wl,--export-all-symbols",
+ "-Wl,--enable-auto-import",
+ "-Wl,--whole-archive",
+ "-Wl,--no-whole-archive",
+ "-Wl,--exclude-libs,All"]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/XSD_toolchain_gcc Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,5 @@
+
+ <xsd:attribute name="Compiler" type="xsd:string" use="optional" default="gcc"/>
+ <xsd:attribute name="CFLAGS" type="xsd:string" use="required"/>
+ <xsd:attribute name="Linker" type="xsd:string" use="optional" default="ld"/>
+ <xsd:attribute name="LDFLAGS" type="xsd:string" use="required"/>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Xenomai/XSD Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,14 @@
+
+ <xsd:element name="Xenomai">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Connection">
+ <xsd:complexType>
+ <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ %(toolchain_gcc)s
+ <xsd:attribute name="xeno_config" type="xsd:string" use="optional" default="/usr/xenomai/"/>
+ </xsd:complexType>
+ </xsd:element>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Xenomai/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,1 @@
+from target_xenomai import *
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Xenomai/target_xenomai.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,3 @@
+class xenomai_target(targets.target_gcc):
+ extensionexe = ""
+ extensiondll = ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/__init__.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialisation
+#import targets
+
+"""
+Beremiz Targets
+
+- Target are python packages, containing at least one "XSD" file
+- Target class may inherit from a toolchain_(toolchainname)
+- The target folder's name must match to name define in the XSD for TargetType
+"""
+
+from os import listdir, path
+
+_base_path = path.split(__file__)[0]
+
+targets = [name for name in listdir(_base_path) if path.isdir(path.join(_base_path, name)) and name.upper() != "CVS" and not name.startswith("__")]
+toolchains = [name for name in listdir(_base_path) if not path.isdir(path.join(_base_path, name)) and name.upper() != "CVS" and name.endswith(".py") and not name.startswith("__") and not name.endswith(".pyc")]
+
+DictXSD_toolchain = {}
+DictXSD_target = {}
+
+targetchoices = ""
+
+# Get all xsd toolchains
+for toolchain in toolchains :
+ toolchainname = path.splitext(toolchain)[0]
+ xsdfilename = path.join(_base_path, "XSD_%s"%(toolchainname))
+ if path.isfile(xsdfilename):
+ xsd_toolchain_string = ""
+ for line in open(xsdfilename).readlines():
+ xsd_toolchain_string += line
+ DictXSD_toolchain[toolchainname] = xsd_toolchain_string
+
+# Get all xsd targets
+for targetname in targets:
+ xsdfilename = path.join(_base_path, targetname, "XSD")
+ if path.isfile(xsdfilename):
+ xsd_target_string = ""
+ for line in open(xsdfilename).readlines():
+ xsd_target_string += line
+ DictXSD_target[targetname] = xsd_target_string%DictXSD_toolchain
+
+for target in DictXSD_target.keys():
+ targetchoices += DictXSD_target[target]
+
+from toolchain_gcc import toolchain_gcc
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/toolchain_gcc.py Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,107 @@
+import os
+from wxPopen import ProcessLogger
+import hashlib
+
+class toolchain_gcc():
+ """
+ This abstract class contains GCC specific code.
+ It cannot be used as this and should be inherited in a target specific
+ class such as target_linux or target_win32
+ """
+ def __init__(self, PuginsRootInstance):
+ self.PuginsRootInstance = PuginsRootInstance
+ self.logger = PuginsRootInstance.logger
+ self.exe = PuginsRootInstance.GetProjectName() + self.extension
+ self.buildpath = PuginsRootInstance._getBuildPath()
+ self.exe_path = os.path.join(self.buildpath, self.exe)
+ self.md5key = None
+
+ def GetBinaryCode(self):
+ try:
+ return open(self.exe_path, "rb").read()
+ except Exception, e:
+ return None
+
+ def _GetMD5FileName(self):
+ return os.path.join(self.buildpath, "lastbuildPLC.md5")
+
+ def GetBinaryCodeMD5(self):
+ if self.md5key is not None:
+ return self.md5key
+ else:
+ try:
+ return open(self._GetMD5FileName(), "r").read()
+ except Exception, e:
+ return None
+
+
+ def build(self):
+ # Retrieve toolchain user parameters
+ toolchain_params = self.PuginsRootInstance.BeremizRoot.getTargetType().getcontent()["value"]
+ self.compiler = toolchain_params.getCompiler()
+ self._CFLAGS = toolchain_params.getCFLAGS()
+ self.linker = toolchain_params.getLinker()
+ self._LDFLAGS = toolchain_params.getLDFLAGS()
+
+ ######### GENERATE OBJECT FILES ########################################
+ obns = []
+ objs = []
+ for Location, CFilesAndCFLAGS, DoCalls in self.PuginsRootInstance.LocationCFilesAndCFLAGS:
+ if Location:
+ self.logger.write("Plugin : " + self.PuginsRootInstance.GetChildByIECLocation(Location).GetCurrentName() + " " + str(Location)+"\n")
+ else:
+ self.logger.write("PLC :\n")
+
+ for CFile, CFLAGS in CFilesAndCFLAGS:
+ bn = os.path.basename(CFile)
+ obn = os.path.splitext(bn)[0]+".o"
+ obns.append(obn)
+ self.logger.write(" [CC] "+bn+" -> "+obn+"\n")
+ objectfilename = os.path.splitext(CFile)[0]+".o"
+
+ status, result, err_result = ProcessLogger(
+ self.logger,
+ "\"%s\" -c \"%s\" -o \"%s\" %s %s"%
+ (self.compiler, CFile, objectfilename, self._CFLAGS, CFLAGS)
+ ).spin()
+
+ if status :
+ self.logger.write_error("C compilation of "+ bn +" failed.\n")
+ return False
+ objs.append(objectfilename)
+
+ ######### GENERATE library FILE ########################################
+ # Link all the object files into one binary file
+ self.logger.write("Linking :\n")
+ objstring = []
+
+ # Generate list .o files
+ listobjstring = '"' + '" "'.join(objs) + '"'
+
+ ALLldflags = ' '.join(self.CustomLDFLAGS+self.PuginsRootInstance.LDFLAGS+[self._LDFLAGS])
+
+ self.logger.write(" [CC] " + ' '.join(obns)+" -> " + self.exe + "\n")
+
+ status, result, err_result = ProcessLogger(
+ self.logger,
+ "\"%s\" %s -o \"%s\" %s"%
+ (self.linker,
+ listobjstring,
+ self.exe_path,
+ ALLldflags)
+ ).spin()
+
+ if status :
+ return False
+ else :
+ # Calculate md5 key and get data for the new created PLC
+ data=self.GetBinaryCode()
+ self.md5key = hashlib.md5(data).hexdigest()
+
+ # Store new PLC filename based on md5 key
+ file = open(self._GetMD5FileName(), "w")
+ file.write(self.md5key)
+ file.close()
+
+ return True
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/baseplugin.xml Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="File1" IEC_Channel="0"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/cfile.xml Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<CFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="cext_xsd.xsd">
+ <includes>
+<![CDATA[]]>
+ </includes>
+ <variables/>
+ <globals>
+<![CDATA[]]>
+ </globals>
+ <initFunction>
+<![CDATA[]]>
+ </initFunction>
+ <cleanUpFunction>
+<![CDATA[]]>
+ </cleanUpFunction>
+ <retrieveFunction>
+<![CDATA[]]>
+ </retrieveFunction>
+ <publishFunction>
+<![CDATA[]]>
+ </publishFunction>
+</CFile>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/plugin.xml Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CExtension CFLAGS="" LDFLAGS=""/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/linux/autom_ihm_rmll/CFileTest@c_ext/baseplugin.xml Wed Aug 20 00:11:40 2008 +0200
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="CFileTest" IEC_Channel="2"/>
--- a/tests/linux/autom_ihm_rmll/beremiz.xml Tue Aug 12 16:27:07 2008 +0200
+++ b/tests/linux/autom_ihm_rmll/beremiz.xml Wed Aug 20 00:11:40 2008 +0200
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
-<BeremizRoot CFLAGS="" Linker="g++" LDFLAGS="">
+<BeremizRoot>
<TargetType>
- <Linux Nice="0"/>
+ <Linux CFLAGS="-g" Linker="g++" LDFLAGS="">
+ <Connection URI_location="PYRO://192.168.0.6:3000"/>
+ </Linux>
</TargetType>
- <Connection>
- <Local/>
- </Connection>
</BeremizRoot>
--- a/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds Tue Aug 12 16:27:07 2008 +0200
+++ b/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds Wed Aug 20 00:11:40 2008 +0200
@@ -4,11 +4,11 @@
FileRevision=1
EDSVersion=4.0
Description=
-CreationTime=03:59PM
-CreationDate=06-28-2008
+CreationTime=04:36PM
+CreationDate=07-25-2008
CreatedBy=CANFestival
-ModificationTime=03:59PM
-ModificationDate=06-28-2008
+ModificationTime=04:36PM
+ModificationDate=07-25-2008
ModifiedBy=CANFestival
[DeviceInfo]
@@ -117,7 +117,7 @@
[OptionalObjects]
SupportedObjects=2
1=0x1017
-2=0x1280
+2=0x1200
[1017]
ParameterName=Producer Heartbeat Time
@@ -127,50 +127,43 @@
DefaultValue=0
PDOMapping=0
-[1280]
-ParameterName=Client SDO 1 Parameter
+[1200]
+ParameterName=Server SDO Parameter
ObjectType=0x8
-SubNumber=4
-
-[1280sub0]
+SubNumber=3
+
+[1200sub0]
ParameterName=Number of Entries
ObjectType=0x7
DataType=0x0005
AccessType=ro
-DefaultValue=3
-PDOMapping=0
-
-[1280sub1]
-ParameterName=COB ID Client to Server (Transmit SDO)
-ObjectType=0x7
-DataType=0x0007
-AccessType=rw
-DefaultValue=0
-PDOMapping=0
-
-[1280sub2]
-ParameterName=COB ID Server to Client (Receive SDO)
-ObjectType=0x7
-DataType=0x0007
-AccessType=rw
-DefaultValue=0
-PDOMapping=0
-
-[1280sub3]
-ParameterName=Node ID of the SDO Server
-ObjectType=0x7
-DataType=0x0005
-AccessType=rw
-DefaultValue=0
+DefaultValue=2
+PDOMapping=0
+
+[1200sub1]
+ParameterName=COB ID Client to Server (Receive SDO)
+ObjectType=0x7
+DataType=0x0007
+AccessType=ro
+DefaultValue=$NODEID+0x600
+PDOMapping=0
+
+[1200sub2]
+ParameterName=COB ID Server to Client (Transmit SDO)
+ObjectType=0x7
+DataType=0x0007
+AccessType=ro
+DefaultValue=$NODEID+0x580
PDOMapping=0
[ManufacturerObjects]
-SupportedObjects=5
+SupportedObjects=6
1=0x2000
2=0x2001
3=0x2002
4=0x2003
5=0x2004
+6=0x2005
[2000]
ParameterName=pump
@@ -211,3 +204,11 @@
AccessType=rw
DefaultValue=0
PDOMapping=1
+
+[2005]
+ParameterName=test64
+ObjectType=0x7
+DataType=0x0015
+AccessType=rw
+DefaultValue=0
+PDOMapping=1
--- a/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/master.od Tue Aug 12 16:27:07 2008 +0200
+++ b/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/master.od Wed Aug 20 00:11:40 2008 +0200
@@ -1,10 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">
-<PyObject module="node" class="Node" id="158606924">
-<attr name="Profile" type="dict" id="158611084" >
+<PyObject module="node" class="Node" id="158559244">
+<attr name="Profile" type="dict" id="158569716" >
</attr>
<attr name="Description" type="string" value="" />
-<attr name="Dictionary" type="dict" id="158610812" >
+<attr name="Dictionary" type="dict" id="158569444" >
<entry>
<key type="numeric" value="4096" />
<val type="numeric" value="0" />
@@ -15,32 +15,32 @@
</entry>
<entry>
<key type="numeric" value="4120" />
- <val type="list" id="157899276" >
- <item type="numeric" value="0" />
- <item type="numeric" value="0" />
- <item type="numeric" value="0" />
- <item type="numeric" value="0" />
- </val>
- </entry>
-</attr>
-<attr name="SpecificMenu" type="list" id="157899020" >
-</attr>
-<attr name="ParamsDictionary" type="dict" id="158610404" >
-</attr>
-<attr name="UserMapping" type="dict" id="158610132" >
-</attr>
-<attr name="DS302" type="dict" id="157920356" >
+ <val type="list" id="157924460" >
+ <item type="numeric" value="0" />
+ <item type="numeric" value="0" />
+ <item type="numeric" value="0" />
+ <item type="numeric" value="0" />
+ </val>
+ </entry>
+</attr>
+<attr name="SpecificMenu" type="list" id="157227212" >
+</attr>
+<attr name="ParamsDictionary" type="dict" id="158569036" >
+</attr>
+<attr name="UserMapping" type="dict" id="158568764" >
+</attr>
+<attr name="DS302" type="dict" id="157947396" >
<entry>
<key type="numeric" value="7968" />
- <val type="dict" id="157943164" >
+ <val type="dict" id="157949708" >
<entry>
<key type="string" value="need" />
<val type="False" value="" />
</entry>
<entry>
<key type="string" value="values" />
- <val type="list" id="157899404" >
- <item type="dict" id="158611628" >
+ <val type="list" id="157924588" >
+ <item type="dict" id="158570260" >
<entry>
<key type="string" value="access" />
<val type="string" value="ro" />
@@ -58,7 +58,7 @@
<val type="string" value="Number of Entries" />
</entry>
</item>
- <item type="dict" id="158610268" >
+ <item type="dict" id="158568900" >
<entry>
<key type="string" value="access" />
<val type="string" value="rw" />
@@ -94,15 +94,15 @@
</entry>
<entry>
<key type="numeric" value="7969" />
- <val type="dict" id="158610676" >
+ <val type="dict" id="158569308" >
<entry>
<key type="string" value="need" />
<val type="False" value="" />
</entry>
<entry>
<key type="string" value="values" />
- <val type="list" id="157899628" >
- <item type="dict" id="158611900" >
+ <val type="list" id="157924844" >
+ <item type="dict" id="158570532" >
<entry>
<key type="string" value="access" />
<val type="string" value="ro" />
@@ -120,7 +120,7 @@
<val type="string" value="Number of Entries" />
</entry>
</item>
- <item type="dict" id="158612172" >
+ <item type="dict" id="158570804" >
<entry>
<key type="string" value="access" />
<val type="string" value="rw" />
@@ -156,15 +156,15 @@
</entry>
<entry>
<key type="numeric" value="7970" />
- <val type="dict" id="158612308" >
+ <val type="dict" id="158570940" >
<entry>
<key type="string" value="need" />
<val type="False" value="" />
</entry>
<entry>
<key type="string" value="values" />
- <val type="list" id="157899692" >
- <item type="dict" id="158611220" >
+ <val type="list" id="157924908" >
+ <item type="dict" id="158569852" >
<entry>
<key type="string" value="access" />
<val type="string" value="ro" />
@@ -182,7 +182,7 @@
<val type="string" value="Number of Entries" />
</entry>
</item>
- <item type="dict" id="158612580" >
+ <item type="dict" id="158571212" >
<entry>
<key type="string" value="access" />
<val type="string" value="rw" />
--- a/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/nodelist.cpj Tue Aug 12 16:27:07 2008 +0200
+++ b/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/nodelist.cpj Wed Aug 20 00:11:40 2008 +0200
@@ -2,6 +2,6 @@
NetName=None
Nodes=0x01
Node3Present=0x01
-Node3Name=myslave
+Node3Name=MySlave
Node3DCFName=Slave_2_0.eds
EDSBaseName=eds
--- a/tests/linux/autom_ihm_rmll/plc.xml Tue Aug 12 16:27:07 2008 +0200
+++ b/tests/linux/autom_ihm_rmll/plc.xml Wed Aug 20 00:11:40 2008 +0200
@@ -8,7 +8,7 @@
productVersion="1"
creationDateTime="2008-06-28 15:43:31"/>
<contentHeader name="autom_ihm_rmll"
- modificationDateTime="2008-06-28 18:26:40">
+ modificationDateTime="2008-08-20 00:50:31">
<coordinateInfo>
<pageSize x="700" y="1000"/>
<fbd>
@@ -65,11 +65,15 @@
<BOOL/>
</type>
</variable>
- <variable name="full_in" address="%IX1.0.3.8194.0">
- <type>
- <BOOL/>
- </type>
- </variable>
+ </localVars>
+ <externalVars>
+ <variable name="full_in">
+ <type>
+ <BOOL/>
+ </type>
+ </variable>
+ </externalVars>
+ <localVars>
<variable name="empty_in" address="%IX1.0.3.8195.0">
<type>
<BOOL/>
@@ -787,7 +791,34 @@
<task name="matache" interval="00:00:00.100000" priority="0">
<pouInstance name="moninst" type="main"/>
</task>
+ <globalVars>
+ <variable name="full_in" address="%IX1.0.3.8194.0">
+ <type>
+ <BOOL/>
+ </type>
+ </variable>
+ <variable name="tyto">
+ <type>
+ <INT/>
+ </type>
+ <initialValue>
+ <simpleValue value="2"/>
+ </initialValue>
+ </variable>
+ </globalVars>
</resource>
+ <globalVars>
+ <variable name="popy">
+ <type>
+ <INT/>
+ </type>
+ </variable>
+ <variable name="fulfuck" address="%IX1.0.3.8194.0">
+ <type>
+ <BOOL/>
+ </type>
+ </variable>
+ </globalVars>
</configuration>
</configurations>
</instances>