etisserant@58: #!/usr/bin/env python
etisserant@58: # -*- coding: utf-8 -*-
etisserant@58: 
etisserant@58: #This file is part of Beremiz, a Integrated Development Environment for
etisserant@58: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
etisserant@58: #
etisserant@58: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
etisserant@58: #
etisserant@58: #See COPYING file for copyrights details.
etisserant@58: #
etisserant@58: #This library is free software; you can redistribute it and/or
etisserant@58: #modify it under the terms of the GNU General Public
etisserant@58: #License as published by the Free Software Foundation; either
etisserant@58: #version 2.1 of the License, or (at your option) any later version.
etisserant@58: #
etisserant@58: #This library is distributed in the hope that it will be useful,
etisserant@58: #but WITHOUT ANY WARRANTY; without even the implied warranty of
etisserant@58: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
etisserant@58: #General Public License for more details.
etisserant@58: #
etisserant@58: #You should have received a copy of the GNU General Public
etisserant@58: #License along with this library; if not, write to the Free Software
etisserant@58: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
etisserant@58: 
etisserant@58: from types import *
etisserant@58: 
etisserant@58: # Translation between IEC types and Can Open types
etisserant@58: IECToCOType = {"BOOL":0x01, "SINT":0x02, "INT":0x03,"DINT":0x04,"LINT":0x10,
etisserant@58:                "USINT":0x05,"UINT":0x06,"UDINT":0x07,"ULINT":0x1B,"REAL":0x08,
etisserant@58:                "LREAL":0x11,"STRING":0x09,"BYTE":0x05,"WORD":0x06,"DWORD":0x07,
etisserant@58:                "LWORD":0x1B,"WSTRING":0x0B}
etisserant@58: 
etisserant@58: # Constants for PDO types 
etisserant@58: RPDO = 1
etisserant@58: TPDO = 2
etisserant@58: 
etisserant@58: SlavePDOType = {"I" : TPDO, "Q" : RPDO}
etisserant@58: InvertPDOType = {RPDO : TPDO, TPDO : RPDO}
greg@340: PDOTypeBaseIndex = {RPDO : 0x1400, TPDO : 0x1800}
greg@340: PDOTypeBaseCobId = {RPDO : 0x200, TPDO : 0x180}
etisserant@58: 
etisserant@58: VariableIncrement = 0x100
etisserant@58: VariableStartIndex = {TPDO : 0x2000, RPDO : 0x4000}
etisserant@58: VariableDirText = {TPDO : "__I", RPDO : "__Q"}
etisserant@58: VariableTypeOffset = dict(zip(["","X","B","W","D","L"], range(6)))
etisserant@58: 
etisserant@58: TrashVariables = [(1, 0x01), (8, 0x05), (16, 0x06), (32, 0x07), (64, 0x1B)]
etisserant@58: 
greg@340: #-------------------------------------------------------------------------------
greg@340: #                  Specific exception for PDO mapping errors
greg@340: #-------------------------------------------------------------------------------
greg@340: 
greg@340: class PDOmappingException(Exception):
greg@340:     pass
greg@340: 
greg@340: 
etisserant@58: def LE_to_BE(value, size):
etisserant@58:     """
etisserant@58:     Convert Little Endian to Big Endian
etisserant@58:     @param value: value expressed in integer
etisserant@58:     @param size: number of bytes generated
etisserant@58:     @return: a string containing the value converted
etisserant@58:     """
etisserant@58:     
etisserant@58:     data = ("%" + str(size * 2) + "." + str(size * 2) + "X") % value
etisserant@58:     list_car = [data[i:i+2] for i in xrange(0, len(data), 2)]
etisserant@58:     list_car.reverse()
etisserant@58:     return "".join([chr(int(car, 16)) for car in list_car])
etisserant@58: 
etisserant@58: 
etisserant@58: def GetNodePDOIndexes(node, type, parameters = False):
etisserant@58:     """
etisserant@58:     Find the PDO indexes of a node
etisserant@58:     @param node: node 
etisserant@58:     @param type: type of PDO searched (RPDO or TPDO or both)
etisserant@58:     @param parameters: indicate which indexes are expected (PDO paramaters : True or PDO mappings : False)
etisserant@58:     @return: a list of indexes found
etisserant@58:     """
etisserant@58:     
etisserant@58:     indexes = []
etisserant@58:     if type & RPDO:
etisserant@58:         indexes.extend([idx for idx in node.GetIndexes() if 0x1400 <= idx <= 0x15FF])
etisserant@58:     if type & TPDO:
etisserant@58:         indexes.extend([idx for idx in node.GetIndexes() if 0x1800 <= idx <= 0x19FF])
etisserant@58:     if not parameters:
etisserant@58:         return [idx + 0x200 for idx in indexes]
etisserant@58:     else:
etisserant@58:         return indexes
etisserant@58: 
etisserant@58: 
etisserant@58: def SearchNodePDOMapping(loc_infos, node):
etisserant@58:     """
etisserant@58:     Find the PDO indexes of a node
etisserant@58:     @param node: node 
etisserant@58:     @param type: type of PDO searched (RPDO or TPDO or both)
etisserant@58:     @param parameters: indicate which indexes are expected (PDO paramaters : True or PDO mappings : False)
etisserant@58:     @return: a list of indexes found
etisserant@58:     """
etisserant@58:     
lbessard@270:     model = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8)
etisserant@58:     
etisserant@58:     for PDOidx in GetNodePDOIndexes(node, loc_infos["pdotype"]):
etisserant@58:         values = node.GetEntry(PDOidx)
etisserant@58:         if values != None:
etisserant@58:             for subindex, mapping in enumerate(values):
lbessard@270:                 if subindex != 0 and mapping & 0xFFFFFF00 == model:
etisserant@58:                     return PDOidx, subindex
etisserant@58:     return None
etisserant@58: 
etisserant@58: 
etisserant@58: def GeneratePDOMappingDCF(idx, cobid, transmittype, pdomapping):
etisserant@58:     """
etisserant@58:     Build concise DCF value for configuring a PDO
etisserant@58:     @param idx: index of PDO parameters
etisserant@58:     @param cobid: PDO generated COB ID
etisserant@58:     @param transmittype : PDO transmit type
etisserant@58:     @param pdomapping: list of PDO mappings
etisserant@58:     @return: a tuple of value and number of parameters to add to DCF 
etisserant@58:     """
etisserant@58:     
etisserant@58:     # Create entry for RPDO or TPDO parameters and Disable PDO
greg@340:     dcfdata = LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)
etisserant@58:     # Set Transmit type synchrone
etisserant@58:     dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)
etisserant@58:     # Re-Enable PDO
etisserant@58:     #         ---- INDEX -----   --- SUBINDEX ----   ----- SIZE ------   ------ DATA ------
greg@340:     dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)
etisserant@58:     nbparams = 3
greg@340:     if len(pdomapping) > 0:
greg@340:         dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)
etisserant@58:         nbparams += 1
greg@340:         # Map Variables
greg@340:         for subindex, (name, loc_infos) in enumerate(pdomapping):
greg@340:             value = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8) + loc_infos["size"]
greg@340:             dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)
greg@340:             nbparams += 1
etisserant@58:     return dcfdata, nbparams
etisserant@58: 
etisserant@58: class ConciseDCFGenerator:
etisserant@58: 
etisserant@58:     def __init__(self, nodelist, nodename):
etisserant@58:         # Dictionary of location informations classed by name
etisserant@58:         self.IECLocations = {}
etisserant@58:         # Dictionary of location that have not been mapped yet
etisserant@58:         self.LocationsNotMapped = {}
etisserant@58:         # Dictionary of location informations classed by name
etisserant@58:         self.MasterMapping = {}
etisserant@58:         # List of COB IDs available
etisserant@58:         self.ListCobIDAvailable = range(0x180, 0x580)
etisserant@58:         # Dictionary of mapping value where unexpected variables are stored
etisserant@58:         self.TrashVariables = {}
etisserant@163:         # Dictionary of pointed variables
etisserant@163:         self.PointedVariables = {}
etisserant@58:         
etisserant@58:         self.NodeList = nodelist
etisserant@58:         self.Manager = self.NodeList.Manager
etisserant@58:         self.MasterNode = self.Manager.GetCurrentNodeCopy()
etisserant@58:         self.MasterNode.SetNodeName(nodename)
etisserant@58:         self.PrepareMasterNode()
etisserant@58: 
etisserant@163:     def GetPointedVariables(self):
etisserant@163:         return self.PointedVariables
etisserant@58:     
etisserant@58:     def RemoveUsedNodeCobId(self, node):
etisserant@58:         """
etisserant@58:         Remove all PDO COB ID used by the given node from the list of available COB ID
etisserant@58:         @param node: node
etisserant@58:         @return: a tuple of number of RPDO and TPDO for the node
etisserant@58:         """
etisserant@58:         
etisserant@58:         # Get list of all node TPDO and RPDO indexes
etisserant@58:         nodeRpdoIndexes = GetNodePDOIndexes(node, RPDO, True)
etisserant@58:         nodeTpdoIndexes = GetNodePDOIndexes(node, TPDO, True)
etisserant@58:         
etisserant@58:         # Mark all the COB ID of the node already mapped PDO as not available
etisserant@58:         for PdoIdx in nodeRpdoIndexes + nodeTpdoIndexes:
etisserant@58:             pdo_cobid = node.GetEntry(PdoIdx, 0x01)
etisserant@58:             # Extract COB ID, if PDO isn't active
etisserant@58:             if pdo_cobid > 0x600 :
etisserant@58:                 pdo_cobid -= 0x80000000
etisserant@58:             # Remove COB ID from the list of available COB ID
etisserant@58:             if pdo_cobid in self.ListCobIDAvailable:
etisserant@58:                 self.ListCobIDAvailable.remove(pdo_cobid)
etisserant@58:         
etisserant@58:         return len(nodeRpdoIndexes), len(nodeTpdoIndexes)
etisserant@58: 
etisserant@58:     
etisserant@58:     def PrepareMasterNode(self):
etisserant@58:         """
etisserant@58:         Add mandatory entries for DCF generation into MasterNode.
etisserant@58:         """
etisserant@58:         
etisserant@58:         # Adding DCF entry into Master node
etisserant@58:         if not self.MasterNode.IsEntry(0x1F22):
etisserant@58:             self.MasterNode.AddEntry(0x1F22, 1, "")
etisserant@58:         self.Manager.AddSubentriesToCurrent(0x1F22, 127, self.MasterNode)
etisserant@58:         
etisserant@58:         # Adding trash mappable variables for unused mapped datas
etisserant@58:         idxTrashVariables = 0x2000 + self.MasterNode.GetNodeID()
etisserant@58:         # Add an entry for storing unexpected all variable
etisserant@58:         self.Manager.AddMapVariableToCurrent(idxTrashVariables, self.MasterNode.GetNodeName()+"_trashvariables", 3, len(TrashVariables), self.MasterNode)
etisserant@58:         for subidx, (size, typeidx) in enumerate(TrashVariables):
etisserant@58:             # Add a subentry for storing unexpected variable of this size
etisserant@58:             self.Manager.SetCurrentEntry(idxTrashVariables, subidx + 1, "TRASH%d" % size, "name", None, self.MasterNode)
etisserant@58:             self.Manager.SetCurrentEntry(idxTrashVariables, subidx + 1, typeidx, "type", None, self.MasterNode)
etisserant@58:             # Store the mapping value for this entry
etisserant@58:             self.TrashVariables[size] = (idxTrashVariables << 16) + ((subidx + 1) << 8) + size
etisserant@58:         
etisserant@58:         RPDOnumber, TPDOnumber = self.RemoveUsedNodeCobId(self.MasterNode)
etisserant@58:         
etisserant@58:         # Store the indexes of the first RPDO and TPDO available for MasterNode
etisserant@58:         self.CurrentPDOParamsIdx = {RPDO : 0x1400 + RPDOnumber, TPDO : 0x1800 + TPDOnumber}
etisserant@58: 
etisserant@58:         # Prepare MasterNode with all nodelist slaves
etisserant@58:         for idx, (nodeid, nodeinfos) in enumerate(self.NodeList.SlaveNodes.items()):
etisserant@58:             node = nodeinfos["Node"]
etisserant@58:             node.SetNodeID(nodeid)
etisserant@58:             
etisserant@58:             RPDOnumber, TPDOnumber = self.RemoveUsedNodeCobId(node)
etisserant@58:             
etisserant@58:             # Get Slave's default SDO server parameters
etisserant@58:             RSDO_cobid = node.GetEntry(0x1200,0x01)
etisserant@58:             if not RSDO_cobid:
etisserant@58:                 RSDO_cobid = 0x600 + nodeid
etisserant@58:             TSDO_cobid = node.GetEntry(0x1200,0x02)
etisserant@58:             if not TSDO_cobid:
etisserant@58:                 TSDO_cobid = 0x580 + nodeid
etisserant@58:             
etisserant@58:             # Configure Master's SDO parameters entries
etisserant@58:             self.Manager.ManageEntriesOfCurrent([0x1280 + idx], [], self.MasterNode)
etisserant@58:             self.MasterNode.SetEntry(0x1280 + idx, 0x01, RSDO_cobid)
etisserant@58:             self.MasterNode.SetEntry(0x1280 + idx, 0x02, TSDO_cobid)
etisserant@58:             self.MasterNode.SetEntry(0x1280 + idx, 0x03, nodeid)        
etisserant@58:         
etisserant@58:     
etisserant@58:     def GetMasterNode(self):
etisserant@58:         """
etisserant@58:         Return MasterNode.
etisserant@58:         """
etisserant@58:         return self.MasterNode
etisserant@58:     
etisserant@58:     def AddParamsToDCF(self, nodeid, data, nbparams):
etisserant@58:         """
etisserant@155:         Add entry to DCF, for the requested nodeID
etisserant@58:         @param nodeid: id of the slave (int)
etisserant@58:         @param data: data to add to slave DCF (string)
etisserant@58:         @param nbparams: number of params added to slave DCF (int)
etisserant@58:         """
etisserant@58:         # Get current DCF for slave
etisserant@58:         nodeDCF = self.MasterNode.GetEntry(0x1F22, nodeid)
etisserant@58:         
etisserant@58:         # Extract data and number of params in current DCF
etisserant@58:         if nodeDCF != None and nodeDCF != '':
etisserant@58:             tmpnbparams = [i for i in nodeDCF[:4]]
etisserant@58:             tmpnbparams.reverse()
etisserant@58:             nbparams += int(''.join(["%2.2x"%ord(i) for i in tmpnbparams]), 16)
etisserant@58:             data = nodeDCF[4:] + data
etisserant@58:         
etisserant@58:         # Build new DCF
etisserant@58:         dcf = LE_to_BE(nbparams, 0x04) + data
etisserant@58:         # Set new DCF for slave
etisserant@58:         self.MasterNode.SetEntry(0x1F22, nodeid, dcf)
etisserant@58:     
greg@340:     def GetEmptyPDO(self, nodeid, pdotype, start_index=None):
greg@340:         """
greg@340:         Search a not configured PDO for a slave
greg@340:         @param node: the slave node object
greg@340:         @param pdotype: type of PDO to generated (RPDO or TPDO)
greg@340:         @param start_index: Index where search must start (default: None)
greg@340:         @return tuple of PDO index, COB ID and number of subindex defined
greg@340:         """
greg@340:         # If no start_index defined, start with PDOtype base index
greg@340:         if start_index is None:
greg@340:             index = PDOTypeBaseIndex[pdotype]
greg@340:         else:
greg@340:             index = start_index
greg@340:         
greg@340:         # Search for all PDO possible index until find a configurable PDO
greg@340:         # starting from start_index
greg@340:         while index < PDOTypeBaseIndex[pdotype] + 0x200:
greg@340:             values = self.NodeList.GetSlaveNodeEntry(nodeid, index + 0x200)
greg@340:             if values != None and values[0] > 0:
greg@340:                 # Check that all subindex upper than 0 equal 0 => configurable PDO
greg@340:                 if reduce(lambda x, y: x and y, map(lambda x: x == 0, values[1:]), True):
greg@340:                     cobid = self.NodeList.GetSlaveNodeEntry(nodeid, index, 1)
greg@340:                     # If no COB ID defined in PDO, generate a new one (not used)
greg@340:                     if cobid == 0:
greg@340:                         if len(self.ListCobIDAvailable) == 0:
greg@340:                             return None
greg@340:                         # Calculate COB ID from standard values
greg@340:                         if index < PDOTypeBaseIndex[pdotype] + 4:
greg@340:                             cobid = PDOTypeBaseCobId[pdotype] + 0x100 * (index - PDOTypeBaseIndex[pdotype]) + nodeid
greg@340:                         if cobid not in self.ListCobIDAvailable:
greg@340:                             cobid = self.ListCobIDAvailable.pop(0)
greg@340:                     return index, cobid, values[0]
greg@340:             index += 1
greg@340:         return None
greg@340:     
greg@340:     def AddPDOMapping(self, nodeid, pdotype, pdoindex, pdocobid, pdomapping, sync_TPDOs):
etisserant@58:         """
etisserant@155:         Record a new mapping request for a slave, and add related slave config to the DCF
etisserant@58:         @param nodeid: id of the slave (int)
etisserant@58:         @param pdotype: type of PDO to generated (RPDO or TPDO)
etisserant@58:         @param pdomapping: list od variables to map with PDO
etisserant@58:         """
greg@340:         # Add an entry to MasterMapping
greg@340:         self.MasterMapping[pdocobid] = {"type" : InvertPDOType[pdotype], 
greg@340:             "mapping" : [None] + [(loc_infos["type"], name) for name, loc_infos in pdomapping]}
greg@340:         
greg@340:         # Return the data to add to DCF
greg@340:         if sync_TPDOs:
greg@340:             return GeneratePDOMappingDCF(pdoindex, pdocobid, 0x01, pdomapping)
greg@340:         else:
greg@340:             return GeneratePDOMappingDCF(pdoindex, pdocobid, 0xFF, pdomapping)
etisserant@58:         return 0, ""
etisserant@58:     
etisserant@58:     def GenerateDCF(self, locations, current_location, sync_TPDOs):
etisserant@58:         """
etisserant@58:         Generate Concise DCF of MasterNode for the locations list given
etisserant@58:         @param locations: list of locations to be mapped
etisserant@58:         @param current_location: tuple of the located prefixes not to be considered
etisserant@58:         @param sync_TPDOs: indicate if TPDO must be synchronous
etisserant@58:         """
etisserant@58:         
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         #               Verify that locations correspond to real slave variables
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         
etisserant@58:         # Get list of locations check if exists and mappables -> put them in IECLocations
etisserant@58:         for location in locations:
etisserant@58:             COlocationtype = IECToCOType[location["IEC_TYPE"]]
etisserant@58:             name = location["NAME"]
etisserant@58:             if name in self.IECLocations:
etisserant@58:                 if self.IECLocations[name]["type"] != COlocationtype:
laurent@361:                     raise PDOmappingException, _("Conflict type for location \"%s\"") % name 
etisserant@58:             else:
etisserant@58:                 # Get only the part of the location that concern this node
etisserant@58:                 loc = location["LOC"][len(current_location):]
etisserant@58:                 # loc correspond to (ID, INDEX, SUBINDEX [,BIT])
etisserant@166:                 if len(loc) not in (2, 3, 4):
laurent@361:                     raise PDOmappingException, _("Bad location size : %s") % str(loc)
etisserant@166:                 elif len(loc) == 2:
etisserant@166:                     continue
etisserant@58:                 
etisserant@58:                 direction = location["DIR"]
etisserant@58:                 
etisserant@58:                 sizelocation = location["SIZE"]
etisserant@58:                 
etisserant@58:                 # Extract and check nodeid
etisserant@58:                 nodeid, index, subindex = loc[:3]
etisserant@58:                 
etisserant@58:                 # Check Id is in slave node list
etisserant@58:                 if nodeid not in self.NodeList.SlaveNodes.keys():
laurent@361:                     raise PDOmappingException, _("Non existing node ID : %d (variable %s)") % (nodeid,name)
etisserant@58:                 
etisserant@58:                 # Get the model for this node (made from EDS)
etisserant@58:                 node = self.NodeList.SlaveNodes[nodeid]["Node"]
etisserant@58:                 
etisserant@58:                 # Extract and check index and subindex
etisserant@58:                 if not node.IsEntry(index, subindex):
laurent@361:                     raise PDOmappingException, _("No such index/subindex (%x,%x) in ID : %d (variable %s)") % (index,subindex,nodeid,name)
etisserant@58:                 
etisserant@58:                 # Get the entry info
etisserant@58:                 subentry_infos = node.GetSubentryInfos(index, subindex)
etisserant@58:                 
etisserant@58:                 # If a PDO mappable
etisserant@58:                 if subentry_infos and subentry_infos["pdo"]:
etisserant@58:                     if sizelocation == "X" and len(loc) > 3:
lbessard@61:                         numbit = loc[3]
etisserant@58:                     elif sizelocation != "X" and len(loc) > 3:
laurent@361:                         raise PDOmappingException, _("Cannot set bit offset for non bool '%s' variable (ID:%d,Idx:%x,sIdx:%x))") % (name,nodeid,index,subindex)
etisserant@58:                     else:
etisserant@58:                         numbit = None
etisserant@58:                     
etisserant@166:                     if location["IEC_TYPE"] != "BOOL" and subentry_infos["type"] != COlocationtype:
laurent@361:                         raise PDOmappingException, _("Invalid type \"%s\"-> %d != %d  for location\"%s\"") % (location["IEC_TYPE"], COlocationtype, subentry_infos["type"] , name)
etisserant@58:                     
etisserant@58:                     typeinfos = node.GetEntryInfos(COlocationtype)
etisserant@58:                     self.IECLocations[name] = {"type":COlocationtype, "pdotype":SlavePDOType[direction],
etisserant@58:                                                 "nodeid": nodeid, "index": index,"subindex": subindex,
etisserant@58:                                                 "bit": numbit, "size": typeinfos["size"], "sizelocation": sizelocation}
etisserant@58:                 else:
laurent@361:                     raise PDOmappingException, _("Not PDO mappable variable : '%s' (ID:%d,Idx:%x,sIdx:%x))") % (name,nodeid,index,subindex)
etisserant@58:         
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         #                         Search for locations already mapped
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         
etisserant@58:         for name, locationinfos in self.IECLocations.items():
etisserant@58:             node = self.NodeList.SlaveNodes[locationinfos["nodeid"]]["Node"]
etisserant@58:             
etisserant@58:             # Search if slave has a PDO mapping this locations
etisserant@58:             result = SearchNodePDOMapping(locationinfos, node)
etisserant@58:             if result != None:
etisserant@58:                 index, subindex = result
etisserant@58:                 # Get COB ID of the PDO
etisserant@58:                 cobid = self.NodeList.GetSlaveNodeEntry(locationinfos["nodeid"], index - 0x200, 1)
etisserant@58:                 
etisserant@58:                 # Add PDO to MasterMapping
etisserant@58:                 if cobid not in self.MasterMapping.keys():
lbessard@80:                     # Verify that PDO transmit type is conform to sync_TPDOs
lbessard@80:                     transmittype = self.NodeList.GetSlaveNodeEntry(locationinfos["nodeid"], index - 0x200, 2)
lbessard@80:                     if sync_TPDOs and transmittype != 0x01 or transmittype != 0xFF:
lbessard@80:                         if sync_TPDOs:
lbessard@80:                             # Change TransmitType to SYNCHRONE
lbessard@80:                             data, nbparams = GeneratePDOMappingDCF(index - 0x200, cobid, 0x01, [])
lbessard@80:                         else:
lbessard@80:                             # Change TransmitType to ASYCHRONE
lbessard@80:                             data, nbparams = GeneratePDOMappingDCF(index - 0x200, cobid, 0xFF, [])
lbessard@80:                         
lbessard@80:                         # Add entry to slave dcf to change transmit type of 
lbessard@80:                         self.AddParamsToDCF(locationinfos["nodeid"], data, nbparams)
lbessard@80:                                     
etisserant@58:                     mapping = [None]
etisserant@58:                     values = node.GetEntry(index)
etisserant@58:                     # Store the size of each entry mapped in PDO
etisserant@58:                     for value in values[1:]:
lbessard@78:                         if value != 0:
lbessard@78:                             mapping.append(value % 0x100)
etisserant@58:                     self.MasterMapping[cobid] = {"type" : InvertPDOType[locationinfos["pdotype"]], "mapping" : mapping}
etisserant@58:             
etisserant@58:                 # Indicate that this PDO entry must be saved
lbessard@270:                 if locationinfos["bit"] is not None:
lbessard@270:                     if not isinstance(self.MasterMapping[cobid]["mapping"][subindex], ListType):
lbessard@270:                         self.MasterMapping[cobid]["mapping"][subindex] = [1] * self.MasterMapping[cobid]["mapping"][subindex]
lbessard@270:                     if locationinfos["bit"] < len(self.MasterMapping[cobid]["mapping"][subindex]):
lbessard@270:                         self.MasterMapping[cobid]["mapping"][subindex][locationinfos["bit"]] = (locationinfos["type"], name)
lbessard@270:                 else:
lbessard@270:                     self.MasterMapping[cobid]["mapping"][subindex] = (locationinfos["type"], name)
etisserant@58:                 
etisserant@58:             else:
etisserant@58:                 # Add location to those that haven't been mapped yet
etisserant@58:                 if locationinfos["nodeid"] not in self.LocationsNotMapped.keys():
etisserant@58:                     self.LocationsNotMapped[locationinfos["nodeid"]] = {TPDO : [], RPDO : []}
etisserant@58:                 self.LocationsNotMapped[locationinfos["nodeid"]][locationinfos["pdotype"]].append((name, locationinfos))
etisserant@58:     
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         #                         Build concise DCF for the others locations
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         
etisserant@58:         for nodeid, locations in self.LocationsNotMapped.items():
lbessard@61:             node = self.NodeList.SlaveNodes[nodeid]["Node"]
etisserant@58:             
etisserant@58:             # Initialize number of params and data to add to node DCF
etisserant@58:             nbparams = 0
etisserant@58:             dataparams = ""
etisserant@58:             
etisserant@58:             # Generate the best PDO mapping for each type of PDO
etisserant@58:             for pdotype in (TPDO, RPDO):
greg@340:                 if len(locations[pdotype]) > 0:
greg@340:                     pdosize = 0
greg@340:                     pdomapping = []
greg@340:                     result = self.GetEmptyPDO(nodeid, pdotype)
greg@340:                     if result is None:
laurent@361:                         raise PDOmappingException, _("Impossible to define PDO mapping for node %02x") % nodeid
greg@340:                     pdoindex, pdocobid, pdonbparams = result
greg@340:                     for name, loc_infos in locations[pdotype]:
greg@340:                         pdosize += loc_infos["size"]
greg@340:                         # If pdo's size > 64 bits
greg@340:                         if pdosize > 64 or len(pdomapping) >= pdonbparams:
greg@340:                             # Generate a new PDO Mapping
greg@340:                             data, nbaddedparams = self.AddPDOMapping(nodeid, pdotype, pdoindex, pdocobid, pdomapping, sync_TPDOs)
greg@340:                             dataparams += data
greg@340:                             nbparams += nbaddedparams
greg@340:                             pdosize = loc_infos["size"]
greg@340:                             pdomapping = [(name, loc_infos)]
greg@340:                             result = self.GetEmptyPDO(nodeid, pdotype, pdoindex + 1)
greg@340:                             if result is None:
laurent@361:                                 raise PDOmappingException, _("Impossible to define PDO mapping for node %02x") % nodeid
greg@340:                             pdoindex, pdocobid, pdonbparams = result
greg@340:                         else:
greg@340:                             pdomapping.append((name, loc_infos))
greg@340:                     # If there isn't locations yet but there is still a PDO to generate
greg@340:                     if len(pdomapping) > 0:
etisserant@58:                         # Generate a new PDO Mapping
greg@340:                         data, nbaddedparams = self.AddPDOMapping(nodeid, pdotype, pdoindex, pdocobid, pdomapping, sync_TPDOs)
etisserant@58:                         dataparams += data
etisserant@58:                         nbparams += nbaddedparams
greg@340:                 
etisserant@58:             # Add number of params and data to node DCF
etisserant@58:             self.AddParamsToDCF(nodeid, dataparams, nbparams)
etisserant@58:         
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         #                         Master Node Configuration
etisserant@58:         #-------------------------------------------------------------------------------
etisserant@58:         
etisserant@58:         # Generate Master's Configuration from informations stored in MasterMapping
etisserant@58:         for cobid, pdo_infos in self.MasterMapping.items():
etisserant@58:             # Get next PDO index in MasterNode for this PDO type
etisserant@58:             current_idx = self.CurrentPDOParamsIdx[pdo_infos["type"]]
etisserant@58:             
etisserant@58:             # Search if there is already a PDO in MasterNode with this cob id
etisserant@58:             for idx in GetNodePDOIndexes(self.MasterNode, pdo_infos["type"], True):
etisserant@58:                 if self.MasterNode.GetEntry(idx, 1) == cobid:
etisserant@58:                     current_idx = idx
etisserant@58:             
etisserant@58:             # Add a PDO to MasterNode if not PDO have been found
etisserant@58:             if current_idx == self.CurrentPDOParamsIdx[pdo_infos["type"]]:
etisserant@58:                 addinglist = [current_idx, current_idx + 0x200]
etisserant@58:                 self.Manager.ManageEntriesOfCurrent(addinglist, [], self.MasterNode)
etisserant@58:                 self.MasterNode.SetEntry(current_idx, 0x01, cobid)
etisserant@58:                 
etisserant@58:                 # Increment the number of PDO for this PDO type
etisserant@58:                 self.CurrentPDOParamsIdx[pdo_infos["type"]] += 1
etisserant@58:             
etisserant@58:             # Change the transmit type of the PDO
etisserant@58:             if sync_TPDOs:
etisserant@58:                 self.MasterNode.SetEntry(current_idx, 0x02, 0x01)
etisserant@58:             else:
etisserant@58:                 self.MasterNode.SetEntry(current_idx, 0x02, 0xFF)
etisserant@58:             
lbessard@270:             mapping = []
lbessard@270:             for item in pdo_infos["mapping"]:
lbessard@270:                 if isinstance(item, ListType):
lbessard@270:                     mapping.extend(item)
lbessard@270:                 else:
lbessard@270:                     mapping.append(item)
lbessard@270:             
etisserant@58:             # Add some subentries to PDO mapping if there is not enough
lbessard@270:             if len(mapping) > 1:
lbessard@270:                 self.Manager.AddSubentriesToCurrent(current_idx + 0x200, len(mapping) - 1, self.MasterNode)
etisserant@58:             
etisserant@58:             # Generate MasterNode's PDO mapping
lbessard@270:             for subindex, variable in enumerate(mapping):
etisserant@58:                 if subindex == 0:
etisserant@58:                     continue
etisserant@58:                 new_index = False
etisserant@58:                 
lbessard@225:                 if isinstance(variable, (IntType, LongType)):
etisserant@58:                     # If variable is an integer then variable is unexpected
etisserant@58:                     self.MasterNode.SetEntry(current_idx + 0x200, subindex, self.TrashVariables[variable])
etisserant@58:                 else:
etisserant@58:                     typeidx, varname = variable
etisserant@58:                     variable_infos = self.IECLocations[varname]
etisserant@58:                     
etisserant@58:                     # Calculate base index for storing variable
etisserant@58:                     mapvariableidx = VariableStartIndex[variable_infos["pdotype"]] + \
etisserant@58:                                      VariableTypeOffset[variable_infos["sizelocation"]] * VariableIncrement + \
etisserant@58:                                      variable_infos["nodeid"]
etisserant@58:                     
etisserant@163:                     # Generate entry name
etisserant@163:                     indexname = "%s%s%s_%d"%(VariableDirText[variable_infos["pdotype"]],
etisserant@163:                                                  variable_infos["sizelocation"],
etisserant@163:                                                  '_'.join(map(str,current_location)),
etisserant@163:                                                  variable_infos["nodeid"])    
etisserant@163:                     
etisserant@58:                     # Search for an entry that has an empty subindex 
etisserant@58:                     while mapvariableidx < VariableStartIndex[variable_infos["pdotype"]] + 0x2000:
etisserant@58:                         # Entry doesn't exist
etisserant@58:                         if not self.MasterNode.IsEntry(mapvariableidx):    
etisserant@58:                             # Add entry to MasterNode
etisserant@163:                             self.Manager.AddMapVariableToCurrent(mapvariableidx, "beremiz"+indexname, 3, 1, self.MasterNode)
etisserant@58:                             new_index = True
etisserant@58:                             nbsubentries = self.MasterNode.GetEntry(mapvariableidx, 0x00)
etisserant@58:                         else:
etisserant@58:                             # Get Number of subentries already defined
etisserant@58:                             nbsubentries = self.MasterNode.GetEntry(mapvariableidx, 0x00)
etisserant@58:                             # if entry is full, go to next entry possible or stop now
etisserant@58:                             if nbsubentries == 0xFF:
etisserant@58:                                 mapvariableidx += 8 * VariableIncrement
etisserant@58:                             else:
etisserant@58:                                 break
etisserant@58:                                 
etisserant@58:                     # Verify that a not full entry has been found
etisserant@58:                     if mapvariableidx < VariableStartIndex[variable_infos["pdotype"]] + 0x2000:
etisserant@58:                         # Generate subentry name
etisserant@58:                         if variable_infos["bit"] != None:
etisserant@58:                             subindexname = "%(index)d_%(subindex)d_%(bit)d"%variable_infos
etisserant@58:                         else:
etisserant@58:                             subindexname = "%(index)d_%(subindex)d"%variable_infos
etisserant@58:                         # If entry have just been created, no subentry have to be added
etisserant@58:                         if not new_index:
etisserant@58:                             self.Manager.AddSubentriesToCurrent(mapvariableidx, 1, self.MasterNode)
etisserant@58:                             nbsubentries += 1
etisserant@58:                         # Add informations to the new subentry created
etisserant@58:                         self.MasterNode.SetMappingEntry(mapvariableidx, nbsubentries, values = {"name" : subindexname})
etisserant@58:                         self.MasterNode.SetMappingEntry(mapvariableidx, nbsubentries, values = {"type" : typeidx})
etisserant@58:                         
etisserant@58:                         # Set value of the PDO mapping
etisserant@58:                         typeinfos = self.Manager.GetEntryInfos(typeidx)
etisserant@58:                         if typeinfos != None:
etisserant@58:                             value = (mapvariableidx << 16) + ((nbsubentries) << 8) + typeinfos["size"]
etisserant@58:                             self.MasterNode.SetEntry(current_idx + 0x200, subindex, value)
etisserant@163:                         
etisserant@163:                         # Add variable to pointed variables
etisserant@163:                         self.PointedVariables[(mapvariableidx, nbsubentries)] = "%s_%s"%(indexname, subindexname)
etisserant@58: 
etisserant@58: def GenerateConciseDCF(locations, current_location, nodelist, sync_TPDOs, nodename):
etisserant@58:     """
etisserant@58:     Fills a CanFestival network editor model, with DCF with requested PDO mappings.
etisserant@58:     @param locations: List of complete variables locations \
etisserant@58:         [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...)
etisserant@58:         "NAME" : name of the variable (generally "__IW0_1_2" style)
etisserant@58:         "DIR" : direction "Q","I" or "M"
etisserant@58:         "SIZE" : size "X", "B", "W", "D", "L"
etisserant@58:         "LOC" : tuple of interger for IEC location (0,1,2,...)
etisserant@58:         }, ...]
etisserant@58:     @param nodelist: CanFestival network editor model
etisserant@58:     @return: a modified copy of the given CanFestival network editor model
etisserant@58:     """
etisserant@58:     
etisserant@58:     dcfgenerator = ConciseDCFGenerator(nodelist, nodename)
etisserant@58:     dcfgenerator.GenerateDCF(locations, current_location, sync_TPDOs)
etisserant@307:     masternode,pointers = dcfgenerator.GetMasterNode(), dcfgenerator.GetPointedVariables()
etisserant@307:     # allow access to local OD from Master PLC
etisserant@307:     pointers.update(LocalODPointers(locations, current_location, masternode))
etisserant@307:     return masternode,pointers
etisserant@58: 
etisserant@166: def LocalODPointers(locations, current_location, slave):
etisserant@166:     IECLocations = {}
etisserant@166:     pointers = {}
etisserant@166:     for location in locations:
etisserant@166:         COlocationtype = IECToCOType[location["IEC_TYPE"]]
etisserant@166:         name = location["NAME"]
etisserant@166:         if name in IECLocations:
etisserant@166:             if IECLocations[name] != COlocationtype:
laurent@361:                 raise PDOmappingException, _("Conflict type for location \"%s\"") % name 
etisserant@166:         else:
etisserant@166:             # Get only the part of the location that concern this node
etisserant@166:             loc = location["LOC"][len(current_location):]
etisserant@166:             # loc correspond to (ID, INDEX, SUBINDEX [,BIT])
etisserant@166:             if len(loc) not in (2, 3, 4):
laurent@361:                 raise PDOmappingException, _("Bad location size : %s") % str(loc)
etisserant@166:             elif len(loc) != 2:
etisserant@166:                 continue
etisserant@166:             
etisserant@166:             # Extract and check nodeid
etisserant@166:             index, subindex = loc[:2]
etisserant@166:             
etisserant@166:             # Extract and check index and subindex
etisserant@166:             if not slave.IsEntry(index, subindex):
laurent@361:                 raise PDOmappingException, _("No such index/subindex (%x,%x) (variable %s)") % (index, subindex, name)
etisserant@166:             
etisserant@166:             # Get the entry info
etisserant@166:             subentry_infos = slave.GetSubentryInfos(index, subindex)    
etisserant@166:             if subentry_infos["type"] != COlocationtype:
laurent@361:                 raise PDOmappingException, _("Invalid type \"%s\"-> %d != %d  for location\"%s\"") % (location["IEC_TYPE"], COlocationtype, subentry_infos["type"] , name)
etisserant@166:             
etisserant@166:             IECLocations[name] = COlocationtype
etisserant@166:             pointers[(index, subindex)] = name
etisserant@166:     return pointers
etisserant@166:         
etisserant@58: if __name__ == "__main__":
etisserant@58:     import os, sys, getopt
etisserant@58: 
etisserant@58:     def usage():
etisserant@58:         print """
etisserant@58: Usage of config_utils.py test :
etisserant@58: 
etisserant@58:     %s [options]
etisserant@58: 
etisserant@58: Options:
etisserant@58:     --help  (-h)
etisserant@58:             Displays help informations for config_utils
etisserant@58: 
etisserant@58:     --reset (-r)
etisserant@58:             Reset the reference result of config_utils test.
etisserant@58:             Use with caution. Be sure that config_utils
etisserant@58:             is currently working properly.
etisserant@58: """%sys.argv[0]
etisserant@58:     
etisserant@58:     # Boolean that indicate if reference result must be redefined
etisserant@58:     reset = False
etisserant@58: 
etisserant@58:     # Extract command options
etisserant@58:     try:
etisserant@58:         opts, args = getopt.getopt(sys.argv[1:], "hr", ["help","reset"])
etisserant@58:     except getopt.GetoptError:
etisserant@58:         # print help information and exit:
etisserant@58:         usage()
etisserant@58:         sys.exit(2)
etisserant@58: 
etisserant@58:     # Test each option
etisserant@58:     for o, a in opts:
etisserant@58:         if o in ("-h", "--help"):
etisserant@58:             usage()
etisserant@58:             sys.exit()
etisserant@58:         elif o in ("-r", "--reset"):
etisserant@58:             reset = True
etisserant@58: 
etisserant@58:     # Extract workspace base folder
etisserant@58:     base_folder = sys.path[0]
etisserant@58:     for i in xrange(3):
etisserant@58:         base_folder = os.path.split(base_folder)[0]
etisserant@58:     # Add CanFestival folder to search pathes
etisserant@58:     sys.path.append(os.path.join(base_folder, "CanFestival-3", "objdictgen"))
etisserant@58:     
etisserant@58:     from nodemanager import *
etisserant@58:     from nodelist import *
etisserant@58:     
etisserant@58:     # Open the test nodelist contained into test_config folder
etisserant@58:     manager = NodeManager()
etisserant@58:     nodelist = NodeList(manager)
etisserant@58:     result = nodelist.LoadProject("test_config")
etisserant@58:     
etisserant@58:     # List of locations, we try to map for test
etisserant@58:     locations = [{"IEC_TYPE":"BYTE","NAME":"__IB0_1_64_24576_1","DIR":"I","SIZE":"B","LOC":(0,1,64,24576,1)},
etisserant@58:                  {"IEC_TYPE":"INT","NAME":"__IW0_1_64_25601_2","DIR":"I","SIZE":"W","LOC":(0,1,64,25601,2)},
etisserant@58:                  {"IEC_TYPE":"INT","NAME":"__IW0_1_64_25601_3","DIR":"I","SIZE":"W","LOC":(0,1,64,25601,3)},
etisserant@58:                  {"IEC_TYPE":"INT","NAME":"__QW0_1_64_25617_2","DIR":"Q","SIZE":"W","LOC":(0,1,64,25617,1)},
etisserant@58:                  {"IEC_TYPE":"BYTE","NAME":"__IB0_1_64_24578_1","DIR":"I","SIZE":"B","LOC":(0,1,64,24578,1)},
etisserant@58:                  {"IEC_TYPE":"UDINT","NAME":"__ID0_1_64_25638_1","DIR":"I","SIZE":"D","LOC":(0,1,64,25638,1)},
etisserant@58:                  {"IEC_TYPE":"UDINT","NAME":"__ID0_1_64_25638_2","DIR":"I","SIZE":"D","LOC":(0,1,64,25638,2)},
etisserant@58:                  {"IEC_TYPE":"UDINT","NAME":"__ID0_1_64_25638_3","DIR":"I","SIZE":"D","LOC":(0,1,64,25638,3)},
etisserant@307:                  {"IEC_TYPE":"UDINT","NAME":"__ID0_1_64_25638_4","DIR":"I","SIZE":"D","LOC":(0,1,64,25638,4)},
etisserant@307:                  {"IEC_TYPE":"UDINT","NAME":"__ID0_1_4096_0","DIR":"I","SIZE":"D","LOC":(0,1,4096,0)}]
etisserant@58:     
etisserant@58:     # Generate MasterNode configuration
etisserant@58:     try:
etisserant@307:         masternode, pointedvariables = GenerateConciseDCF(locations, (0, 1), nodelist, True, "TestNode")
etisserant@58:     except ValueError, message:
etisserant@58:         print "%s\nTest Failed!"%message
etisserant@58:         sys.exit()
etisserant@58:     
etisserant@307:     import pprint
etisserant@58:     # Get Text corresponding to MasterNode 
etisserant@307:     result_node = masternode.PrintString()
etisserant@307:     result_vars = pprint.pformat(pointedvariables)
etisserant@307:     result = result_node + "\n********POINTERS*********\n" + result_vars + "\n"
etisserant@58:     
etisserant@58:     # If reset has been choosen
etisserant@58:     if reset:
etisserant@58:         # Write Text into reference result file
etisserant@307:         testfile = open("test_config/result.txt", "w")
etisserant@307:         testfile.write(result)
etisserant@307:         testfile.close()
etisserant@58:         
etisserant@58:         print "Reset Successful!"
etisserant@58:     else:
etisserant@150:         import os
etisserant@150:         
etisserant@307:         testfile = open("test_config/result_tmp.txt", "w")
etisserant@307:         testfile.write(result)
etisserant@307:         testfile.close()
etisserant@58:         
etisserant@150:         os.system("diff test_config/result.txt test_config/result_tmp.txt")
etisserant@150:         os.remove("test_config/result_tmp.txt")