# HG changeset patch # User etisserant # Date 1219183900 -7200 # Node ID cb9901076a21a9de50874c99990ea8129226ea22 # Parent cd81a7a6e55c6137f56a1685d3a9a1bb45108b6a 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 diff -r cd81a7a6e55c -r cb9901076a21 Beremiz.py --- 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() diff -r cd81a7a6e55c -r cb9901076a21 Beremiz_service.py --- /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() diff -r cd81a7a6e55c -r cb9901076a21 Zeroconf.py --- /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 StrangeBerry, + 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() diff -r cd81a7a6e55c -r cb9901076a21 connectors/PYRO/__init__.py --- /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() + + diff -r cd81a7a6e55c -r cb9901076a21 connectors/USB/__init__.py --- /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 diff -r cd81a7a6e55c -r cb9901076a21 connectors/__init__.py --- /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 + diff -r cd81a7a6e55c -r cb9901076a21 discovery.py --- /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() diff -r cd81a7a6e55c -r cb9901076a21 images/Connect.png Binary file images/Connect.png has changed diff -r cd81a7a6e55c -r cb9901076a21 images/Debug.png Binary file images/Debug.png has changed diff -r cd81a7a6e55c -r cb9901076a21 images/Disconnect.png Binary file images/Disconnect.png has changed diff -r cd81a7a6e55c -r cb9901076a21 images/Transfer.png Binary file images/Transfer.png has changed diff -r cd81a7a6e55c -r cb9901076a21 images/icons.svg --- 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 @@ @@ -56,6 +56,30 @@ + + + + + + + + + + + + + + + + + + + + transform="matrix(0.9968636,0,0,0.9968648,-6.725278,-192.08626)"> %% Build Clean editPLC HMIEditor ImportDEF ImportSVG NetworkEdit Run ShowIECcode Stop Unknown %% @@ -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" /> @@ -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" /> @@ -83140,7 +83270,7 @@ + transform="translate(1600.9892,-430.1329)"> ST @@ -83332,7 +83462,7 @@ id="g1161" transform="matrix(0.5724346,-0.3079575,0.3079575,0.5724346,131.42904,887.47867)" /> @@ -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">%% Add Delete Disabled Enabled HideVars IECCDown IECCUp Maximize Minimize minus plus ShowVars %% @@ -83561,7 +83691,7 @@ transform="matrix(4.5011397,0,0,4.5011397,2971.834,-119.97324)" /> + transform="translate(25.999952,-56.02206)"> + transform="translate(19.498009,-60.38403)"> + transform="matrix(0.9968618,0,0,0.996865,-396.72428,-80.086391)"> + transform="matrix(0.9968618,0,0,0.9968618,-339.72428,-100.08488)"> + transform="matrix(0.965737,0,0,0.965737,-233.99669,-100.83328)"> + transform="matrix(1,0,0,-1,-6.571463,624.20104)"> + transform="translate(39.428537,134.5234)"> + transform="matrix(0.9968629,0,0,0.9968629,-119.72484,-178.08517)"> + transform="translate(20,-60.08447)"> + transform="matrix(0.996861,0,0,0.996861,17.276127,-223.08392)"> + transform="matrix(0.996861,0,0,0.996861,58.276127,-243.08387)"> %% Compiler TargetType %% + transform="matrix(9.6211589e-2,0,0,9.6211589e-2,219.72267,266.8136)"> @@ -84440,7 +84570,7 @@ + transform="matrix(5.3097304e-2,0,0,5.3097304e-2,247.38564,260.36282)"> PluginMethods PluginPluginParams + y="279.61035" + x="43.489288">Params Buttons + y="333.61218" + x="37.5">Buttons Beremiz icons Pre-Alpha Release. Copyright © LOLITECH 2008 %% splash %% + y="761.85242" + x="176.98375" + style="font-size:51.04000092px">%% splash %% + transform="matrix(0.2686638,0,0,0.2686638,514.93573,-19.29882)"> %% ico48 ico24 ico16 %% + y="509.33417" + x="26.008392">%% ico48 ico24 ico16 %% + transform="translate(-0.5214,-80.3797)" /> %% editIECrawcode EditCfile %% + x="113.29593" + y="121.52582">%% editIECrawcode EditCfile Transfer Connect Disconnect Debug %% + transform="translate(-520.13257,-140)"> + Icons + + + + + + + + + + diff -r cd81a7a6e55c -r cb9901076a21 plugger.py --- a/plugger.py Tue Aug 12 16:27:07 2008 +0200 +++ b/plugger.py Wed Aug 20 00:11:40 2008 +0200 @@ -70,6 +70,10 @@ def BufferProject(self): pass +# helper func to get path to images +def opjimg(imgname): + return os.path.join("images",imgname) + class PlugTemplate: """ This class is the one that define plugins. @@ -100,9 +104,6 @@ # copy PluginMethods so that it can be later customized self.PluginMethods = [dic.copy() for dic in self.PluginMethods] - def IsGUIPlugin(self): - return False - def PluginBaseXmlFilePath(self, PlugName=None): return os.path.join(self.PlugPath(PlugName), "baseplugin.xml") @@ -112,7 +113,8 @@ def PlugPath(self,PlugName=None): if not PlugName: PlugName = self.BaseParams.getName() - return os.path.join(self.PlugParent.PlugPath(), PlugName + NameTypeSeparator + self.PlugType) + return os.path.join(self.PlugParent.PlugPath(), + PlugName + NameTypeSeparator + self.PlugType) def PlugTestModified(self): return self.ChangesToSave @@ -149,13 +151,13 @@ params.append(self.PlugParams[1].getElementInfos(self.PlugParams[0])) return params - def SetParamsAttribute(self, path, value, logger): + def SetParamsAttribute(self, path, value): self.ChangesToSave = True # Filter IEC_Channel and Name, that have specific behavior if path == "BaseParams.IEC_Channel": - return self.FindNewIEC_Channel(value,logger), True + return self.FindNewIEC_Channel(value), True elif path == "BaseParams.Name": - res = self.FindNewName(value,logger) + res = self.FindNewName(value) self.PlugRequestSave() return res, True @@ -205,7 +207,7 @@ shutil.copytree(src_PlugPath, self.PlugPath) return True - def PlugGenerate_C(self, buildpath, locations, logger): + def PlugGenerate_C(self, buildpath, locations): """ Generate C code @param locations: List of complete variables locations \ @@ -217,12 +219,15 @@ }, ...] @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND """ - logger.write_warning(".".join(map(lambda x:str(x), self.GetCurrentLocation())) + " -> Nothing to do\n") + self.logger.write_warning(".".join(map(lambda x:str(x), self.GetCurrentLocation())) + " -> Nothing to do\n") return [],"",False - def _Generate_C(self, buildpath, locations, logger): - # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS - PlugCFilesAndCFLAGS, PlugLDFLAGS, DoCalls = self.PlugGenerate_C(buildpath, locations, logger) + def _Generate_C(self, buildpath, locations): + # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS, DoCalls, extra_files + # extra_files = [(fname,fobject), ...] + gen_result = self.PlugGenerate_C(buildpath, locations) + PlugCFilesAndCFLAGS, PlugLDFLAGS, DoCalls = gen_result[:3] + extra_files = gen_result[3:] # if some files heve been generated put them in the list with their location if PlugCFilesAndCFLAGS: LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), PlugCFilesAndCFLAGS, DoCalls)] @@ -245,19 +250,18 @@ new_location = PlugChild.GetCurrentLocation() # How deep are we in the tree ? depth=len(new_location) - _LocationCFilesAndCFLAGS, _LDFLAGS = \ + _LocationCFilesAndCFLAGS, _LDFLAGS, _extra_files = \ PlugChild._Generate_C( #keep the same path buildpath, # filter locations that start with current IEC location - [loc for loc in locations if loc["LOC"][0:depth] == new_location ], - #propagete logger - logger) + [loc for loc in locations if loc["LOC"][0:depth] == new_location ]) # stack the result LocationCFilesAndCFLAGS += _LocationCFilesAndCFLAGS LDFLAGS += _LDFLAGS - - return LocationCFilesAndCFLAGS,LDFLAGS + extra_files += _extra_files + + return LocationCFilesAndCFLAGS, LDFLAGS, extra_files def BlockTypesFactory(self): return [] @@ -343,7 +347,7 @@ else: return {"name" : self.BaseParams.getName(), "channel" : self.BaseParams.getIEC_Channel(), "enabled" : self.BaseParams.getEnabled(), "parent" : len(self.PlugChildsTypes) > 0, "type" : self.BaseParams.getName(), "values" : childs} - def FindNewName(self, DesiredName, logger): + def FindNewName(self, DesiredName): """ Changes Name to DesiredName if available, Name-N if not. @param DesiredName: The desired Name (string) @@ -376,10 +380,10 @@ shutil.move(oldname, self.PlugPath()) # warn user he has two left hands if DesiredName != res: - logger.write_warning("A child names \"%s\" already exist -> \"%s\"\n"%(DesiredName,res)) + self.logger.write_warning("A child names \"%s\" already exist -> \"%s\"\n"%(DesiredName,res)) return res - def FindNewIEC_Channel(self, DesiredChannel, logger): + def FindNewIEC_Channel(self, DesiredChannel): """ Changes IEC Channel number to DesiredChannel if available, nearest available if not. @param DesiredChannel: The desired IEC channel (int) @@ -401,15 +405,14 @@ if res < CurrentChannel: # Want to go down ? res -= 1 # Test for n-1 if res < 0 : - if logger : - logger.write_warning("Cannot find lower free IEC channel than %d\n"%CurrentChannel) + self.logger.write_warning("Cannot find lower free IEC channel than %d\n"%CurrentChannel) return CurrentChannel # Can't go bellow 0, do nothing else : # Want to go up ? res += 1 # Test for n-1 # Finally set IEC Channel self.BaseParams.setIEC_Channel(res) - if logger and DesiredChannel != res: - logger.write_warning("A child with IEC channel %d already exist -> %d\n"%(DesiredChannel,res)) + if DesiredChannel != res: + self.logger.write_warning("A child with IEC channel %d already exist -> %d\n"%(DesiredChannel,res)) return res def OnPlugClose(self): @@ -433,7 +436,7 @@ # Ask to his parent to remove it self.PlugParent._doRemoveChild(self) - def PlugAddChild(self, PlugName, PlugType, logger): + def PlugAddChild(self, PlugName, PlugType): """ Create the plugins that may be added as child to this node self @param PlugType: string desining the plugin class name (get name from PlugChildsTypes) @@ -469,6 +472,8 @@ def __init__(_self): # self is the parent _self.PlugParent = self + # self is the parent + _self.logger = self.logger # Keep track of the plugin type name _self.PlugType = PlugType # remind the help string, for more fancy display @@ -476,11 +481,11 @@ # Call the base plugin template init - change XSD into class members PlugTemplate.__init__(_self) # check name is unique - NewPlugName = _self.FindNewName(PlugName, logger) + NewPlugName = _self.FindNewName(PlugName) # If dir have already be made, and file exist if os.path.isdir(_self.PlugPath(NewPlugName)): #and os.path.isfile(_self.PluginXmlFilePath(PlugName)): #Load the plugin.xml file into parameters members - _self.LoadXMLParams(logger, NewPlugName) + _self.LoadXMLParams(NewPlugName) # Basic check. Better to fail immediately. if (_self.BaseParams.getName() != NewPlugName): raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(NewPlugName, _self.BaseParams.getName()) @@ -488,12 +493,12 @@ # Now, self.PlugPath() should be OK # Check that IEC_Channel is not already in use. - _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel(),logger) + _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel()) # Call the plugin real __init__ if getattr(PlugClass, "__init__", None): PlugClass.__init__(_self) #Load and init all the childs - _self.LoadChilds(logger) + _self.LoadChilds() #just loaded, nothing to saved _self.ChangesToSave = False else: @@ -519,42 +524,44 @@ return newPluginOpj - def LoadXMLParams(self, logger, PlugName = None): + def LoadXMLParams(self, PlugName = None): methode_name = os.path.join(self.PlugPath(PlugName), "methods.py") if os.path.isfile(methode_name): execfile(methode_name) # Get the base xml tree if self.MandatoryParams: - #try: + try: basexmlfile = open(self.PluginBaseXmlFilePath(PlugName), 'r') basetree = minidom.parse(basexmlfile) self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0]) basexmlfile.close() - #except Exception, e: - # logger.write_error("Couldn't load plugin base parameters %s :\n %s" % (PlugName, str(e))) - + except Exception, exc: + self.logger.write_error("Couldn't load plugin base parameters %s :\n %s" % (PlugName, str(exc))) + self.logger.write_error(traceback.format_exc()) # Get the xml tree if self.PlugParams: - #try: + try: xmlfile = open(self.PluginXmlFilePath(PlugName), 'r') tree = minidom.parse(xmlfile) self.PlugParams[1].loadXMLTree(tree.childNodes[0]) xmlfile.close() - #except Exception, e: - # logger.write_error("Couldn't load plugin parameters %s :\n %s" % (PlugName, str(e))) - - def LoadChilds(self, logger): + except Exception, exc: + self.logger.write_error("Couldn't load plugin parameters %s :\n %s" % (PlugName, str(exc))) + self.logger.write_error(traceback.format_exc()) + + def LoadChilds(self): # Iterate over all PlugName@PlugType in plugin directory, and try to open them for PlugDir in os.listdir(self.PlugPath()): if os.path.isdir(os.path.join(self.PlugPath(), PlugDir)) and \ PlugDir.count(NameTypeSeparator) == 1: pname, ptype = PlugDir.split(NameTypeSeparator) - #try: - self.PlugAddChild(pname, ptype, logger) - #except Exception, e: - # logger.write_error("Could not add child \"%s\", type %s :\n%s\n"%(pname, ptype, str(e))) + try: + self.PlugAddChild(pname, ptype) + except Exception, exc: + self.logger.write_error("Could not add child \"%s\", type %s :\n%s\n"%(pname, ptype, str(exc))) + self.logger.write_error(traceback.format_exc()) def EnableMethod(self, method, value): for d in self.PluginMethods: @@ -563,6 +570,13 @@ return True return False + def ShowMethod(self, method, value): + for d in self.PluginMethods: + if d["method"]==method: + d["shown"]=value + return True + return False + def _GetClassFunction(name): def GetRootClass(): return getattr(__import__("plugins." + name), name).RootClass @@ -586,15 +600,24 @@ ieclib_path = os.path.join(base_folder, "matiec", "lib") # import for project creation timestamping +from threading import Timer from time import localtime from datetime import datetime # import necessary stuff from PLCOpenEditor from PLCControler import PLCControler from PLCOpenEditor import PLCOpenEditor, ProjectDialog from TextViewer import TextViewer -from plcopen.structures import IEC_KEYWORDS +from plcopen.structures import IEC_KEYWORDS, TypeHierarchy_list + +# Construct debugger natively supported types +DebugTypes = [t for t in zip(*TypeHierarchy_list)[0] if not t.startswith("ANY")] + \ + ["STEP","TRANSITION","ACTION"] + import runtime import re +import targets +import connectors +from discovery import DiscoveryDialog class PluginsRoot(PlugTemplate, PLCControler): """ @@ -619,77 +642,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + """+targets.targetchoices+""" - - - - - - - - - - - - """ - def __init__(self, frame): + def __init__(self, frame, logger): PLCControler.__init__(self) self.MandatoryParams = None self.AppFrame = frame - - """ - This method are not called here... but in NewProject and OpenProject - self._AddParamsMembers() - self.PluggedChilds = {} - """ + self.logger = logger + self._builder = None + self._connector = None + + # Setup debug information + self.IECdebug_callables = {} + # Timer to prevent rapid-fire when registering many variables + self.DebugTimer=Timer(0.5,self.RegisterDebugVarToConnector) + self.ResetIECProgramsAndVariables() + + + #This method are not called here... but in NewProject and OpenProject + #self._AddParamsMembers() + #self.PluggedChilds = {} + # In both new or load scenario, no need to save self.ChangesToSave = False # root have no parent @@ -769,14 +751,14 @@ self.SaveProject() return None - def LoadProject(self, ProjectPath, logger): + def LoadProject(self, ProjectPath): """ Load a project contained in a folder @param ProjectPath: path of the project folder """ if os.path.basename(ProjectPath) == "": ProjectPath = os.path.dirname(ProjectPath) - # Verify that project contains a PLCOpen program + # Verify that project contains a PLCOpen program plc_file = os.path.join(ProjectPath, "plc.xml") if not os.path.isfile(plc_file): return "Folder choosen doesn't contain a program. It's not a valid project!" @@ -792,12 +774,19 @@ # If dir have already be made, and file exist if os.path.isdir(self.PlugPath()) and os.path.isfile(self.PluginXmlFilePath()): #Load the plugin.xml file into parameters members - result = self.LoadXMLParams(logger) + result = self.LoadXMLParams() if result: return result #Load and init all the childs - self.LoadChilds(logger) + self.LoadChilds() self.RefreshPluginsBlockLists() + + if os.path.exists(self._getBuildPath()): + self.EnableMethod("_Clean", True) + + if os.path.isfile(self._getIECrawcodepath()): + self.ShowMethod("_showIECcode", True) + return None def SaveProject(self): @@ -827,19 +816,12 @@ def PluginXmlFilePath(self, PlugName=None): return os.path.join(self.PlugPath(PlugName), "beremiz.xml") - def PlugGenerate_C(self, buildpath, locations, logger): - """ - Generate C code - @param locations: List of complete variables locations \ - [(IEC_loc, IEC_Direction, IEC_Type, Name)]\ - ex: [((0,0,4,5),'I','STRING','__IX_0_0_4_5'),...] - @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND - """ - return [(C_file_name, self.CFLAGS) for C_file_name in self.PLCGeneratedCFiles ] , "", False - def _getBuildPath(self): return os.path.join(self.ProjectPath, "build") + def _getExtraFilesPath(self): + return os.path.join(self._getBuildPath(), "extra_files") + def _getIECcodepath(self): # define name for IEC code file return os.path.join(self._getBuildPath(), "plc.st") @@ -850,7 +832,7 @@ def _getIECrawcodepath(self): # define name for IEC raw code file - return os.path.join(self._getBuildPath(), "raw_plc.st") + return os.path.join(self.PlugPath(), "raw_plc.st") def GetLocations(self): locations = [] @@ -877,23 +859,22 @@ locations.append(resdict) return locations - def _Generate_SoftPLC(self, logger): + def _Generate_SoftPLC(self): """ Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C @param buildpath: path where files should be created - @param logger: the log pseudo file """ # Update PLCOpenEditor Plugin Block types before generate ST code self.RefreshPluginsBlockLists() - logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n") + self.logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n") buildpath = self._getBuildPath() # ask PLCOpenEditor controller to write ST/IL/SFC code file result = self.GenerateProgram(self._getIECgeneratedcodepath()) if result is not None: # Failed ! - logger.write_error("Error in ST/IL/SFC code generator :\n%s\n"%result) + self.logger.write_error("Error in ST/IL/SFC code generator :\n%s\n"%result) return False plc_file = open(self._getIECcodepath(), "w") if os.path.isfile(self._getIECrawcodepath()): @@ -901,11 +882,11 @@ plc_file.write("\n") plc_file.write(open(self._getIECgeneratedcodepath(), "r").read()) plc_file.close() - logger.write("Compiling IEC Program in to C code...\n") + self.logger.write("Compiling IEC Program in to C code...\n") # Now compile IEC code into many C files # files are listed to stdout, and errors to stderr. status, result, err_result = ProcessLogger( - logger, + self.logger, "\"%s\" -f \"%s\" -I \"%s\" \"%s\""%( iec2c_path, self._getIECcodepath(), @@ -913,27 +894,238 @@ no_stdout=True).spin() if status: # Failed ! - logger.write_error("Error : IEC to C compiler returned %d\n"%status) + self.logger.write_error("Error : IEC to C compiler returned %d\n"%status) return False # Now extract C files of stdout C_files = [ fname for fname in result.splitlines() if fname[-2:]==".c" or fname[-2:]==".C" ] # remove those that are not to be compiled because included by others C_files.remove("POUS.c") if not C_files: - logger.write_error("Error : At least one configuration and one ressource must be declared in PLC !\n") + self.logger.write_error("Error : At least one configuration and one ressource must be declared in PLC !\n") return False # transform those base names to full names with path C_files = map(lambda filename:os.path.join(buildpath, filename), C_files) - logger.write("Extracting Located Variables...\n") + self.logger.write("Extracting Located Variables...\n") # Keep track of generated located variables for later use by self._Generate_C self.PLCGeneratedLocatedVars = self.GetLocations() # Keep track of generated C files for later use by self.PlugGenerate_C self.PLCGeneratedCFiles = C_files # compute CFLAGS for plc - self.CFLAGS = "\"-I"+ieclib_path+"\"" + self.plcCFLAGS = "\"-I"+ieclib_path+"\"" return True - def _build(self, logger): + def GetBuilder(self): + """ + Return a Builder (compile C code into machine code) + """ + # Get target, module and class name + targetname = self.BeremizRoot.getTargetType().getcontent()["name"] + modulename = "targets." + targetname + classname = targetname + "_target" + + # Get module reference + try : + targetmodule = getattr(__import__(modulename), targetname) + + except Exception, msg: + self.logger.write_error("Can't find module for target %s!\n"%targetname) + self.logger.write_error(str(msg)) + return None + + # Get target class + targetclass = getattr(targetmodule, classname) + + # if target already + if self._builder is None or not isinstance(self._builder,targetclass): + # Get classname instance + self._builder = targetclass(self) + return self._builder + + def GetLastBuildMD5(self): + builder=self.GetBuilder() + if builder is not None: + return builder.GetBinaryCodeMD5() + else: + return None + + ####################################################################### + # + # C CODE GENERATION METHODS + # + ####################################################################### + + def PlugGenerate_C(self, buildpath, locations): + """ + Return C code generated by iec2c compiler + when _generate_softPLC have been called + @param locations: ignored + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [(C_file_name, self.plcCFLAGS) for C_file_name in self.PLCGeneratedCFiles ] , "-lrt", False + + def ResetIECProgramsAndVariables(self): + """ + Reset variable and program list that are parsed from + CSV file generated by IEC2C compiler. + """ + self._ProgramList = None + self._VariablesList = None + self._IECPathToIdx = None + self._IdxToIECPath = None + + def GetIECProgramsAndVariables(self): + """ + Parse CSV-like file VARIABLES.csv resulting from IEC2C compiler. + Each section is marked with a line staring with '//' + list of all variables used in various POUs + """ + if self._ProgramList is None or self._VariablesList is None: + try: + csvfile = os.path.join(self._getBuildPath(),"VARIABLES.csv") + # describes CSV columns + ProgramsListAttributeName = ["num", "C_path", "type"] + VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"] + self._ProgramList = [] + self._VariablesList = [] + self._IECPathToIdx = {} + self._IdxToIECPath = {} + + # Separate sections + ListGroup = [] + for line in open(csvfile,'r').xreadlines(): + strippedline = line.strip() + if strippedline.startswith("//"): + # Start new section + ListGroup.append([]) + elif len(strippedline) > 0 and len(ListGroup) > 0: + # append to this section + ListGroup[-1].append(strippedline) + + # first section contains programs + for line in ListGroup[0]: + # Split and Maps each field to dictionnary entries + attrs = dict(zip(ProgramsListAttributeName,line.strip().split(';'))) + # Truncate "C_path" to remove conf an ressources names + attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:]) + # Push this dictionnary into result. + self._ProgramList.append(attrs) + + # second section contains all variables + for line in ListGroup[1]: + # Split and Maps each field to dictionnary entries + attrs = dict(zip(VariablesListAttributeName,line.strip().split(';'))) + # Truncate "C_path" to remove conf an ressources names + attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:]) + # Push this dictionnary into result. + self._VariablesList.append(attrs) + # Fill in IEC<->C translation dicts + IEC_path=attrs["IEC_path"] + Idx=int(attrs["num"]) + self._IECPathToIdx[IEC_path]=Idx + self._IdxToIECPath[Idx]=IEC_path + except Exception,e: + self.logger.write_error("Cannot open/parse VARIABLES.csv!\n") + self.logger.write_error(traceback.format_exc()) + self.ResetIECProgramsAndVariables() + return False + + return True + + def Generate_plc_debugger(self): + """ + Generate trace/debug code out of PLC variable list + """ + self.GetIECProgramsAndVariables() + + # prepare debug code + debug_code = runtime.code("plc_debug") % { + "programs_declarations": + "\n".join(["extern %(type)s %(C_path)s;"%p for p in self._ProgramList]), + "extern_variables_declarations":"\n".join([ + {"PT":"extern %(type)s *%(C_path)s;", + "VAR":"extern %(type)s %(C_path)s;"}[v["vartype"]]%v + for v in self._VariablesList if v["C_path"].find('.')<0]), + "subscription_table_count": + len(self._VariablesList), + "variables_pointer_type_table_count": + len(self._VariablesList), + "variables_pointer_type_table_initializer":"\n".join([ + {"PT":" variable_table[%(num)s].ptrvalue = (void*)(%(C_path)s);\n", + "VAR":" variable_table[%(num)s].ptrvalue = (void*)(&%(C_path)s);\n"}[v["vartype"]]%v + + " variable_table[%(num)s].type = %(type)s_ENUM;\n"%v + for v in self._VariablesList if v["type"] in DebugTypes ])} + + return debug_code + + def RegisterDebugVarToConnector(self): + Idxs = [] + if self._connector is not None: + for IECPath,WeakCallableDict in self.IECdebug_callables: + if len(WeakCallableDict) == 0: + # Callable Dict is empty. + # This variable is not needed anymore! + self.IECdebug_callables.pop(IECPath) + else: + # Convert + Idx = self._IECPathToIdx.get(IECPath,None) + if Idx is not None: + Idxs.append(Idx) + else: + self.logger.write_warning("Debug : Unknown variable %s\n"%IECPath) + self._connector.TraceVariables(Idxs) + + def SubscribeDebugIECVariable(self, IECPath, callable, *args, **kwargs): + """ + Dispatching use a dictionnary linking IEC variable paths + to a WeakKeyDictionary linking + weakly referenced callables to optionnal args + """ + # If no entry exist, create a new one with a fresh WeakKeyDictionary + self.IECdebug_callables.setdefault( + IECPath, + WeakKeyDictionary())[callable]=(args, kwargs) + # Rearm anti-rapid-fire timer + self.DebugTimer.cancel() + self.DebugTimer.start() + + def Generate_plc_common_main(self): + """ + Use plugins layout given in LocationCFilesAndCFLAGS to + generate glue code that dispatch calls to all plugins + """ + # filter location that are related to code that will be called + # in retreive, publish, init, cleanup + locstrs = map(lambda x:"_".join(map(str,x)), + [loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls]) + + # Generate main, based on template + plc_main_code = runtime.code("plc_common_main") % { + "calls_prototypes":"\n".join([( + "int __init_%(s)s(int argc,char **argv);\n"+ + "void __cleanup_%(s)s();\n"+ + "void __retrieve_%(s)s();\n"+ + "void __publish_%(s)s();")%{'s':locstr} for locstr in locstrs]), + "retrieve_calls":"\n ".join([ + "__retrieve_%s();"%locstr for locstr in locstrs]), + "publish_calls":"\n ".join([ #Call publish in reverse order + "__publish_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]), + "init_calls":"\n ".join([ + "init_level=%d; "%i+ + "if(res = __init_%s(argc,argv)){"%locstr + + #"printf(\"%s\"); "%locstr + #for debug + "return res;}" for i,locstr in enumerate(locstrs)]), + "cleanup_calls":"\n ".join([ + "if(init_level >= %d) "%i+ + "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]) + } + + target_name = self.BeremizRoot.getTargetType().getcontent()["name"] + plc_main_code += runtime.code("plc_%s_main"%target_name) + + return plc_main_code + + + def _build(self): """ Method called by user to (re)build SoftPLC and plugin tree """ @@ -945,113 +1137,90 @@ # Eventually create build dir if not os.path.exists(buildpath): os.mkdir(buildpath) - - logger.flush() - logger.write("Start build in %s\n" % buildpath) - + # There is something to clean self.EnableMethod("_Clean", True) - self.EnableMethod("_showIECcode", True) - - # Generate SoftPLC code - if not self._Generate_SoftPLC(logger): - logger.write_error("SoftPLC code generation failed !\n") + + self.logger.flush() + self.logger.write("Start build in %s\n" % buildpath) + + # Generate SoftPLC IEC code + IECGenRes = self._Generate_SoftPLC() + self.ShowMethod("_showIECcode", True) + + # If IEC code gen fail, bail out. + if not IECGenRes: + self.logger.write_error("IEC-61131-3 code generation failed !\n") return False - - #logger.write("SoftPLC code generation successfull\n") - - logger.write("Generating plugins code ...\n") + # Reset variable and program list that are parsed from + # CSV file generated by IEC2C compiler. + self.ResetIECProgramsAndVariables() # Generate C code and compilation params from plugin hierarchy + self.logger.write("Generating plugins C code\n") try: - LocationCFilesAndCFLAGS,LDFLAGS = self._Generate_C( + self.LocationCFilesAndCFLAGS, self.LDFLAGS, ExtraFiles = self._Generate_C( buildpath, - self.PLCGeneratedLocatedVars, - logger) + self.PLCGeneratedLocatedVars) except Exception, exc: - logger.write_error("Plugins code generation Failed !\n") - logger.write_error(traceback.format_exc()) + self.logger.write_error("Plugins code generation failed !\n") + self.logger.write_error(traceback.format_exc()) return False - - #debug - #import pprint - #pp = pprint.PrettyPrinter(indent=4) - #logger.write("LocationCFilesAndCFLAGS :\n"+pp.pformat(LocationCFilesAndCFLAGS)+"\n") - #logger.write("LDFLAGS :\n"+pp.pformat(LDFLAGS)+"\n") - - # Generate main - locstrs = map(lambda x:"_".join(map(str,x)), [loc for loc,Cfiles,DoCalls in LocationCFilesAndCFLAGS if loc and DoCalls]) - plc_main = runtime.code("plc_common_main") % { - "calls_prototypes":"\n".join( - ["int __init_%(s)s(int argc,char **argv);\nvoid __cleanup_%(s)s();\nvoid __retrieve_%(s)s();\nvoid __publish_%(s)s();"% - {'s':locstr} for locstr in locstrs]), - "retrieve_calls":"\n ".join(["__retrieve_%(s)s();"%{'s':locstr} for locstr in locstrs]), - "publish_calls":"\n ".join(["__publish_%(s)s();"%{'s':locstr} for locstr in locstrs]), - "init_calls":"\n ".join(["init_level++; if(res = __init_%(s)s(argc,argv)) return res;"%{'s':locstr} for locstr in locstrs]), - "cleanup_calls":"\n ".join(["if(init_level-- > 0) __cleanup_%(s)s();"%{'s':locstr} for locstr in locstrs]), - "sync_align_ratio":self.BeremizRoot.getSync_Align_Ratio()} - target_name = self.BeremizRoot.TargetType.content["name"] - plc_main += runtime.code("plc_%s_main"%target_name) - - main_path = os.path.join(buildpath, "main.c" ) - f = open(main_path,'w') - f.write(plc_main) - f.close() - # First element is necessarely root - LocationCFilesAndCFLAGS[0][1].insert(0,(main_path, self.CFLAGS)) - - # Compile the resulting code into object files. - compiler = self.BeremizRoot.getCompiler() - _CFLAGS = self.BeremizRoot.getCFLAGS() - linker = self.BeremizRoot.getLinker() - _LDFLAGS = self.BeremizRoot.getLDFLAGS() - obns = [] - objs = [] - for Location, CFilesAndCFLAGS, DoCalls in LocationCFilesAndCFLAGS: - if Location: - logger.write("Plugin : " + self.GetChildByIECLocation(Location).GetCurrentName() + " " + str(Location)+"\n") - else: - logger.write("PLC :\n") - - for CFile, CFLAGS in CFilesAndCFLAGS: - bn = os.path.basename(CFile) - obn = os.path.splitext(bn)[0]+".o" - obns.append(obn) - logger.write(" [CC] "+bn+" -> "+obn+"\n") - objectfilename = os.path.splitext(CFile)[0]+".o" - - status, result, err_result = ProcessLogger( - logger, - "\"%s\" -c \"%s\" -o \"%s\" %s %s"% - (compiler, CFile, objectfilename, _CFLAGS, CFLAGS) - ).spin() - - if status != 0: - logger.write_error("Build failed\n") - return False - objs.append(objectfilename) - # Link all the object files into one executable - logger.write("Linking :\n") - exe = self.GetProjectName() - if target_name == "Win32": - exe += ".exe" - exe_path = os.path.join(buildpath, exe) - logger.write(" [CC] " + ' '.join(obns)+" -> " + exe + "\n") - status, result, err_result = ProcessLogger( - logger, - "\"%s\" \"%s\" -o \"%s\" %s"% - (linker, - '" "'.join(objs), - exe_path, - ' '.join(LDFLAGS+[_LDFLAGS])) - ).spin() - if status != 0: - logger.write_error("Build failed\n") - self.EnableMethod("_Run", False) + # Get temprary directory path + extrafilespath = self._getExtraFilesPath() + # Remove old directory + if os.path.exists(extrafilespath): + shutil.rmtree(extrafilespath) + # Recreate directory + os.mkdir(extrafilespath) + # Then write the files + for fname,fobject in ExtraFiles: + print fname,fobject + fpath = os.path.join(extrafilespath,fname) + open(fpath, "wb").write(fobject.read()) + # Now we can forget ExtraFiles (will close files object) + del ExtraFiles + + # Template based part of C code generation + # files are stacked at the beginning, as files of plugin tree root + for generator, filename, name in [ + # debugger code + (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"), + # init/cleanup/retrieve/publish, run and align code + (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]: + try: + # Do generate + code = generator() + code_path = os.path.join(buildpath,filename) + open(code_path, "w").write(code) + # Insert this file as first file to be compiled at root plugin + self.LocationCFilesAndCFLAGS[0][1].insert(0,(code_path, self.plcCFLAGS)) + except Exception, exc: + self.logger.write_error(name+" generation failed !\n") + self.logger.write_error(traceback.format_exc()) + return False + + self.logger.write("C code generated successfully.\n") + + # Get current or fresh builder + builder = self.GetBuilder() + if builder is None: + self.logger.write_error("Fatal : cannot get builder.\n") return False - - self.EnableMethod("_Run", True) + + # Build + try: + if not builder.build() : + self.logger.write_error("C Build failed.\n") + return False + except Exception, exc: + self.logger.write_error("C Build crashed !\n") + self.logger.write_error(traceback.format_exc()) + return False + + # Update GUI status about need for transfer + self.CompareLocalAndRemotePLC() return True def ShowError(self, logger, from_location, to_location): @@ -1061,8 +1230,8 @@ start = (from_location[0] - start_row, from_location[1] - start_col) end = (to_location[0] - start_row, to_location[1] - start_col) self.PLCEditor.ShowError(infos, start, end) - - def _showIECcode(self, logger): + + def _showIECcode(self): plc_file = self._getIECcodepath() new_dialog = wx.Frame(self.AppFrame) ST_viewer = TextViewer(new_dialog, "", None, None) @@ -1076,14 +1245,9 @@ new_dialog.Show() - def _editIECrawcode(self, logger): + def _editIECrawcode(self): new_dialog = wx.Frame(self.AppFrame) - buildpath = self._getBuildPath() - # Eventually create build dir - if not os.path.exists(buildpath): - os.mkdir(buildpath) - controler = MiniTextControler(self._getIECrawcodepath()) ST_viewer = TextViewer(new_dialog, "", None, controler) #ST_viewer.Enable(False) @@ -1092,7 +1256,7 @@ new_dialog.Show() - def _EditPLC(self, logger): + def _EditPLC(self): if self.PLCEditor is None: self.RefreshPluginsBlockLists() def _onclose(): @@ -1108,83 +1272,245 @@ self.PLCEditor._onsave = _onsave self.PLCEditor.Show() - def _Clean(self, logger): + def _Clean(self): if os.path.isdir(os.path.join(self._getBuildPath())): - logger.write("Cleaning the build directory\n") + self.logger.write("Cleaning the build directory\n") shutil.rmtree(os.path.join(self._getBuildPath())) else: - logger.write_error("Build directory already clean\n") - self.EnableMethod("_showIECcode", False) + self.logger.write_error("Build directory already clean\n") + self.ShowMethod("_showIECcode", False) self.EnableMethod("_Clean", False) - self.EnableMethod("_Run", False) - - def _Run(self, logger): - command_start_plc = os.path.join(self._getBuildPath(),self.GetProjectName() + exe_ext) - if os.path.isfile(command_start_plc): - has_gui_plugin = False - for PlugChild in self.IterChilds(): - has_gui_plugin |= PlugChild.IsGUIPlugin() - logger.write("Starting PLC\n") - def this_plc_finish_callback(*args): - if self.runningPLC is not None: - self.runningPLC = None - self.reset_finished() - self.runningPLC = ProcessLogger( - logger, - command_start_plc, - finish_callback = this_plc_finish_callback, - no_gui=wx.Platform != '__WXMSW__' or not has_gui_plugin) - self.EnableMethod("_Clean", False) - self.EnableMethod("_Run", False) - self.EnableMethod("_Stop", True) - self.EnableMethod("_build", False) + self.CompareLocalAndRemotePLC() + + ############# Real PLC object access ############# + def UpdateMethodsFromPLCStatus(self): + # Get PLC state : Running or Stopped + # TODO : use explicit status instead of boolean + if self._connector is not None: + status = self._connector.GetPLCstatus() + self.logger.write("PLC is %s\n"%status) else: - logger.write_error("%s doesn't exist\n" %command_start_plc) - - def reset_finished(self): - self.EnableMethod("_Clean", True) - self.EnableMethod("_Run", True) - self.EnableMethod("_Stop", False) - self.EnableMethod("_build", True) - - def _Stop(self, logger): - if self.runningPLC is not None: - logger.write("Stopping PLC\n") - was_runningPLC = self.runningPLC - self.runningPLC = None - was_runningPLC.kill() - self.reset_finished() + status = "Disconnected" + for args in { + "Started":[("_Run", False), + ("_Debug", False), + ("_Stop", True)], + "Stopped":[("_Run", True), + ("_Debug", True), + ("_Stop", False)], + "Empty": [("_Run", False), + ("_Debug", False), + ("_Stop", False)], + "Dirty": [("_Run", True), + ("_Debug", True), + ("_Stop", False)], + "Disconnected": [("_Run", False), + ("_Debug", False), + ("_Stop", False)], + }.get(status,[]): + self.ShowMethod(*args) + + def _Run(self): + """ + Start PLC + """ + if self._connector.StartPLC(): + self.logger.write("Starting PLC\n") + else: + self.logger.write_error("Couldn't start PLC !\n") + self.UpdateMethodsFromPLCStatus() + + def _Debug(self): + """ + Start PLC (Debug Mode) + """ + if self.GetIECProgramsAndVariables() and self._connector.StartPLC(): + self.logger.write("Starting PLC (debug mode)\n") + # TODO : laucnch PLCOpenEditor in Debug Mode + self.logger.write_warning("Debug mode for PLCopenEditor not implemented\n") + self.logger.write_warning("Starting alternative test GUI\n") + # TODO : laucnch PLCOpenEditor in Debug Mode + else: + self.logger.write_error("Couldn't start PLC debug !\n") + self.UpdateMethodsFromPLCStatus() + + def _Stop(self): + """ + Stop PLC + """ + if self._connector.StopPLC(): + self.logger.write("Stopping PLC\n") + else: + self.logger.write_error("Couldn't stop PLC !\n") + self.UpdateMethodsFromPLCStatus() + + def _Connect(self): + # don't accept re-connetion is already connected + if self._connector is not None: + self.logger.write_error("Already connected. Please disconnect\n") + return + + # Get connector uri + uri = self.\ + BeremizRoot.\ + getTargetType().\ + getcontent()["value"].\ + getConnection().\ + getURI_location().\ + strip() + + # if uri is empty launch discovery dialog + if uri == "": + # Launch Service Discovery dialog + dia = DiscoveryDialog(self.AppFrame) + dia.ShowModal() + uri = dia.GetResult() + # Nothing choosed or cancel button + if uri is None: + return + else: + self.\ + BeremizRoot.\ + getTargetType().\ + getcontent()["value"].\ + getConnection().\ + setURI_location(uri) + + # Get connector from uri + try: + self._connector = connectors.ConnectorFactory(uri, self) + except Exception, msg: + self.logger.write_error("Exception while connecting %s!\n"%uri) + self.logger.write_error(traceback.format_exc()) + + # Did connection success ? + if self._connector is None: + # Oups. + self.logger.write_error("Connection failed to %s!\n"%uri) + else: + self.ShowMethod("_Connect", False) + self.ShowMethod("_Disconnect", True) + self.ShowMethod("_Transfer", True) + + self.CompareLocalAndRemotePLC() + self.UpdateMethodsFromPLCStatus() + + def CompareLocalAndRemotePLC(self): + if self._connector is None: + return + # We are now connected. Update button status + MD5 = self.GetLastBuildMD5() + # Check remote target PLC correspondance to that md5 + if MD5 is not None: + if not self._connector.MatchMD5(MD5): + self.logger.write_warning( + "Latest build do not match with target, please transfer.\n") + self.EnableMethod("_Transfer", True) + else: + self.logger.write( + "Latest build match target, no transfer needed.\n") + self.EnableMethod("_Transfer", True) + #self.EnableMethod("_Transfer", False) + else: + self.logger.write_warning( + "Cannot compare latest build to target. Please build.\n") + self.EnableMethod("_Transfer", False) + + + def _Disconnect(self): + self._connector = None + self.ShowMethod("_Transfer", False) + self.ShowMethod("_Connect", True) + self.ShowMethod("_Disconnect", False) + self.UpdateMethodsFromPLCStatus() + + def _Transfer(self): + # Get the last build PLC's + MD5 = self.GetLastBuildMD5() + + # Check if md5 file is empty : ask user to build PLC + if MD5 is None : + self.logger.write_error("Failed : Must build before transfer.\n") + return False + + # Compare PLC project with PLC on target + if self._connector.MatchMD5(MD5): + self.logger.write( + "Latest build already match current target. Transfering anyway...\n") + + # Get temprary directory path + extrafilespath = self._getExtraFilesPath() + extrafiles = [(name, open(os.path.join(extrafilespath, name), + 'rb').read()) \ + for name in os.listdir(extrafilespath) \ + if not name=="CVS"] + + for filename, unused in extrafiles: + print filename + + # Send PLC on target + builder = self.GetBuilder() + if builder is not None: + data = builder.GetBinaryCode() + if data is not None : + if self._connector.NewPLC(MD5, data, extrafiles): + self.logger.write("Transfer completed successfully.\n") + else: + self.logger.write_error("Transfer failed\n") + else: + self.logger.write_error("No PLC to transfer (did build success ?)\n") + self.UpdateMethodsFromPLCStatus() PluginMethods = [ - {"bitmap" : os.path.join("images", "editPLC"), + {"bitmap" : opjimg("editPLC"), "name" : "Edit PLC", "tooltip" : "Edit PLC program with PLCOpenEditor", "method" : "_EditPLC"}, - {"bitmap" : os.path.join("images", "Build"), + {"bitmap" : opjimg("Build"), "name" : "Build", "tooltip" : "Build project into build folder", "method" : "_build"}, - {"bitmap" : os.path.join("images", "Clean"), + {"bitmap" : opjimg("Clean"), "name" : "Clean", + "enabled" : False, "tooltip" : "Clean project build folder", "method" : "_Clean"}, - {"bitmap" : os.path.join("images", "Run"), + {"bitmap" : opjimg("Run"), "name" : "Run", - "enabled" : False, - "tooltip" : "Run PLC from build folder", + "shown" : False, + "tooltip" : "Start PLC", "method" : "_Run"}, - {"bitmap" : os.path.join("images", "Stop"), + {"bitmap" : opjimg("Debug"), + "name" : "Debug", + "shown" : False, + "tooltip" : "Start PLC (debug mode)", + "method" : "_Debug"}, + {"bitmap" : opjimg("Stop"), "name" : "Stop", - "enabled" : False, + "shown" : False, "tooltip" : "Stop Running PLC", "method" : "_Stop"}, - {"bitmap" : os.path.join("images", "ShowIECcode"), + {"bitmap" : opjimg("Connect"), + "name" : "Connect", + "tooltip" : "Connect to the target PLC", + "method" : "_Connect"}, + {"bitmap" : opjimg("Transfer"), + "name" : "Transfer", + "shown" : False, + "tooltip" : "Transfer PLC", + "method" : "_Transfer"}, + {"bitmap" : opjimg("Disconnect"), + "name" : "Disconnect", + "shown" : False, + "tooltip" : "Disconnect from PLC", + "method" : "_Disconnect"}, + {"bitmap" : opjimg("ShowIECcode"), "name" : "Show code", - "enabled" : False, + "shown" : False, "tooltip" : "Show IEC code generated by PLCGenerator", "method" : "_showIECcode"}, - {"bitmap" : os.path.join("images", "editIECrawcode"), + {"bitmap" : opjimg("editIECrawcode"), "name" : "Append code", "tooltip" : "Edit raw IEC code added to code generated by PLCGenerator", - "method" : "_editIECrawcode"} + "method" : "_editIECrawcode"}, ] diff -r cd81a7a6e55c -r cb9901076a21 plugins/c_ext/c_ext.py --- 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 diff -r cd81a7a6e55c -r cb9901076a21 plugins/canfestival/canfestival.py --- 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 @@ + + + + + + + + @@ -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"] += ( diff -r cd81a7a6e55c -r cb9901076a21 plugins/canfestival/cf_runtime.c --- 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 diff -r cd81a7a6e55c -r cb9901076a21 plugins/svgui/svgui.py --- 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; } }; diff -r cd81a7a6e55c -r cb9901076a21 runtime/PLCObject.py --- /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() + + + + diff -r cd81a7a6e55c -r cb9901076a21 runtime/ServicePublisher.py --- /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 diff -r cd81a7a6e55c -r cb9901076a21 runtime/__init__.py --- 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 diff -r cd81a7a6e55c -r cb9901076a21 runtime/plc_Linux_main.c --- 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 #include #include +#include + +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); +} diff -r cd81a7a6e55c -r cb9901076a21 runtime/plc_Win32_main.c --- 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 #include -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) diff -r cd81a7a6e55c -r cb9901076a21 runtime/plc_common_main.c --- 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; +} diff -r cd81a7a6e55c -r cb9901076a21 runtime/plc_debug.c --- /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 + +#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; +} + diff -r cd81a7a6e55c -r cb9901076a21 targets/Linux/XSD --- /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 @@ + + + + + + + + + + + %(toolchain_gcc)s + + \ No newline at end of file diff -r cd81a7a6e55c -r cb9901076a21 targets/Linux/__init__.py --- /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"] diff -r cd81a7a6e55c -r cb9901076a21 targets/Rtai/XSD --- /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 @@ + + + + + + + + + + + %(toolchain_gcc)s + + + \ No newline at end of file diff -r cd81a7a6e55c -r cb9901076a21 targets/Rtai/__init__.py --- /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 diff -r cd81a7a6e55c -r cb9901076a21 targets/Rtai/target_rtai.py --- /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" diff -r cd81a7a6e55c -r cb9901076a21 targets/Win32/XSD --- /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 @@ + + + + + + + + + + + %(toolchain_gcc)s + + + diff -r cd81a7a6e55c -r cb9901076a21 targets/Win32/__init__.py --- /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"] diff -r cd81a7a6e55c -r cb9901076a21 targets/XSD_toolchain_gcc --- /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 @@ + + + + + \ No newline at end of file diff -r cd81a7a6e55c -r cb9901076a21 targets/Xenomai/XSD --- /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 @@ + + + + + + + + + + + %(toolchain_gcc)s + + + \ No newline at end of file diff -r cd81a7a6e55c -r cb9901076a21 targets/Xenomai/__init__.py --- /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 diff -r cd81a7a6e55c -r cb9901076a21 targets/Xenomai/target_xenomai.py --- /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 = "" diff -r cd81a7a6e55c -r cb9901076a21 targets/__init__.py --- /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 diff -r cd81a7a6e55c -r cb9901076a21 targets/toolchain_gcc.py --- /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 + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/baseplugin.xml --- /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 @@ + + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/cfile.xml --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/CFileTest@c_ext/File1@C_File/plugin.xml --- /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 @@ + + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/CFileTest@c_ext/baseplugin.xml --- /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 @@ + + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/beremiz.xml --- 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 @@ - + - + + + - - - diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds --- a/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds Tue Aug 12 16:27:07 2008 +0200 +++ b/tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/eds/Slave_2_0.eds Wed Aug 20 00:11:40 2008 +0200 @@ -4,11 +4,11 @@ FileRevision=1 EDSVersion=4.0 Description= -CreationTime=03:59PM -CreationDate=06-28-2008 +CreationTime=04:36PM +CreationDate=07-25-2008 CreatedBy=CANFestival -ModificationTime=03:59PM -ModificationDate=06-28-2008 +ModificationTime=04:36PM +ModificationDate=07-25-2008 ModifiedBy=CANFestival [DeviceInfo] @@ -117,7 +117,7 @@ [OptionalObjects] SupportedObjects=2 1=0x1017 -2=0x1280 +2=0x1200 [1017] ParameterName=Producer Heartbeat Time @@ -127,50 +127,43 @@ DefaultValue=0 PDOMapping=0 -[1280] -ParameterName=Client SDO 1 Parameter +[1200] +ParameterName=Server SDO Parameter ObjectType=0x8 -SubNumber=4 - -[1280sub0] +SubNumber=3 + +[1200sub0] ParameterName=Number of Entries ObjectType=0x7 DataType=0x0005 AccessType=ro -DefaultValue=3 -PDOMapping=0 - -[1280sub1] -ParameterName=COB ID Client to Server (Transmit SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1280sub2] -ParameterName=COB ID Server to Client (Receive SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1280sub3] -ParameterName=Node ID of the SDO Server -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 +DefaultValue=2 +PDOMapping=0 + +[1200sub1] +ParameterName=COB ID Client to Server (Receive SDO) +ObjectType=0x7 +DataType=0x0007 +AccessType=ro +DefaultValue=$NODEID+0x600 +PDOMapping=0 + +[1200sub2] +ParameterName=COB ID Server to Client (Transmit SDO) +ObjectType=0x7 +DataType=0x0007 +AccessType=ro +DefaultValue=$NODEID+0x580 PDOMapping=0 [ManufacturerObjects] -SupportedObjects=5 +SupportedObjects=6 1=0x2000 2=0x2001 3=0x2002 4=0x2003 5=0x2004 +6=0x2005 [2000] ParameterName=pump @@ -211,3 +204,11 @@ AccessType=rw DefaultValue=0 PDOMapping=1 + +[2005] +ParameterName=test64 +ObjectType=0x7 +DataType=0x0015 +AccessType=rw +DefaultValue=0 +PDOMapping=1 diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/master.od --- 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 @@ - - + + - + @@ -15,32 +15,32 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - + + @@ -58,7 +58,7 @@ - + @@ -94,15 +94,15 @@ - + - - + + @@ -120,7 +120,7 @@ - + @@ -156,15 +156,15 @@ - + - - + + @@ -182,7 +182,7 @@ - + diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/canopen@canfestival/master@CanOpenNode/nodelist.cpj --- 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 diff -r cd81a7a6e55c -r cb9901076a21 tests/linux/autom_ihm_rmll/plc.xml --- 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"/> + modificationDateTime="2008-08-20 00:50:31"> @@ -65,11 +65,15 @@ - - - - - + + + + + + + + + @@ -787,7 +791,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +