beremiz

Added concepts :

2008-08-20, etisserant
cb9901076a21
Parents cd81a7a6e55c
Children f572ab819769
Added concepts :
- connector class (for PLC control, debug and flashing abstraction).
- builder class (C toolchain abstraction)

Added features :
- Pyro based connector.
- ctypes loader, load PLC shared object and run it
- ctypes based debugger embryo (not tested)
- gcc builder

Broken:
- Win32 runtime
- Most tests
  • +12 -12
    Beremiz.py
  • +98 -0
    Beremiz_service.py
  • +1556 -0
    Zeroconf.py
  • +106 -0
    connectors/PYRO/__init__.py
  • +22 -0
    connectors/USB/__init__.py
  • +43 -0
    connectors/__init__.py
  • +171 -0
    discovery.py
  • +0 -0
    images/Connect.png
  • +0 -0
    images/Debug.png
  • +0 -0
    images/Disconnect.png
  • +0 -0
    images/Transfer.png
  • +296 -89
    images/icons.svg
  • +605 -279
    plugger.py
  • +3 -3
    plugins/c_ext/c_ext.py
  • +19 -10
    plugins/canfestival/canfestival.py
  • +3 -4
    plugins/canfestival/cf_runtime.c
  • +47 -37
    plugins/svgui/svgui.py
  • +258 -0
    runtime/PLCObject.py
  • +41 -0
    runtime/ServicePublisher.py
  • +3 -0
    runtime/__init__.py
  • +43 -11
    runtime/plc_Linux_main.c
  • +9 -1
    runtime/plc_Win32_main.c
  • +32 -14
    runtime/plc_common_main.c
  • +151 -0
    runtime/plc_debug.c
  • +13 -0
    targets/Linux/XSD
  • +5 -0
    targets/Linux/__init__.py
  • +14 -0
    targets/Rtai/XSD
  • +1 -0
    targets/Rtai/__init__.py
  • +3 -0
    targets/Rtai/target_rtai.py
  • +14 -0
    targets/Win32/XSD
  • +10 -0
    targets/Win32/__init__.py
  • +5 -0
    targets/XSD_toolchain_gcc
  • +14 -0
    targets/Xenomai/XSD
  • +1 -0
    targets/Xenomai/__init__.py
  • +3 -0
    targets/Xenomai/target_xenomai.py
  • +67 -0
    targets/__init__.py
  • +107 -0
    targets/toolchain_gcc.py
  • +2 -0
    tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/baseplugin.xml
  • +22 -0
    tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/cfile.xml
  • +2 -0
    tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/plugin.xml
  • +2 -0
    tests/linux/autom_ihm_rmll/CFileTest@c_ext/baseplugin.xml
  • +4 -5
    tests/linux/autom_ihm_rmll/beremiz.xml
  • +29 -28
    tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds
  • +20 -20
    tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/master.od
  • +1 -1
    tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/nodelist.cpj
  • +33 -2
    tests/linux/autom_ihm_rmll/plc.xml
  • --- 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
    + extra_files += _extra_files
    - return LocationCFilesAndCFLAGS,LDFLAGS
    + 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)))
    + 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, logger):
    + 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
    + self.logger = logger
    + self._builder = None
    + self._connector = None
    - """
    - This method are not called here... but in NewProject and OpenProject
    - self._AddParamsMembers()
    - self.PluggedChilds = {}
    - """
    + # 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)
    + # 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
    - 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"
    + # 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
    - status, result, err_result = ProcessLogger(
    - logger,
    - "\"%s\" -c \"%s\" -o \"%s\" %s %s"%
    - (compiler, CFile, objectfilename, _CFLAGS, CFLAGS)
    - ).spin()
    + 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
    - 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)
    + # 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
    -
    - self.EnableMethod("_Run", True)
    +
    + # 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:
    + 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:
    - logger.write_error("%s doesn't exist\n" %command_start_plc)
    + 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()
    - def reset_finished(self):
    - self.EnableMethod("_Clean", True)
    - self.EnableMethod("_Run", True)
    - self.EnableMethod("_Stop", False)
    - self.EnableMethod("_build", True)
    + # 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 _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()
    + 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
    +SubNumber=3
    -[1280sub0]
    +[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
    +DefaultValue=2
    PDOMapping=0
    -[1280sub2]
    -ParameterName=COB ID Server to Client (Receive SDO)
    +[1200sub1]
    +ParameterName=COB ID Client to Server (Receive SDO)
    ObjectType=0x7
    DataType=0x0007
    -AccessType=rw
    -DefaultValue=0
    +AccessType=ro
    +DefaultValue=$NODEID+0x600
    PDOMapping=0
    -[1280sub3]
    -ParameterName=Node ID of the SDO Server
    +[1200sub2]
    +ParameterName=COB ID Server to Client (Transmit SDO)
    ObjectType=0x7
    -DataType=0x0005
    -AccessType=rw
    -DefaultValue=0
    +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,7 +15,7 @@
    </entry>
    <entry>
    <key type="numeric" value="4120" />
    - <val type="list" id="157899276" >
    + <val type="list" id="157924460" >
    <item type="numeric" value="0" />
    <item type="numeric" value="0" />
    <item type="numeric" value="0" />
    @@ -23,24 +23,24 @@
    </val>
    </entry>
    </attr>
    -<attr name="SpecificMenu" type="list" id="157899020" >
    +<attr name="SpecificMenu" type="list" id="157227212" >
    </attr>
    -<attr name="ParamsDictionary" type="dict" id="158610404" >
    +<attr name="ParamsDictionary" type="dict" id="158569036" >
    </attr>
    -<attr name="UserMapping" type="dict" id="158610132" >
    +<attr name="UserMapping" type="dict" id="158568764" >
    </attr>
    -<attr name="DS302" type="dict" id="157920356" >
    +<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">
    + </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>