etisserant@58: #!/usr/bin/env python
etisserant@58: # -*- coding: utf-8 -*-
etisserant@58: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
etisserant@58: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
etisserant@58: #
andrej@1571: # See COPYING file for copyrights details.
etisserant@58: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
etisserant@58: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
etisserant@58: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
etisserant@58: 
andrej@1826: 
andrej@1881: from __future__ import absolute_import
andrej@1826: from __future__ import print_function
andrej@1834: import os
andrej@1834: import sys
andrej@1834: import getopt
etisserant@58: from types import *
etisserant@58: 
etisserant@58: # Translation between IEC types and Can Open types
andrej@1740: IECToCOType = {
andrej@1740:     "BOOL":    0x01,
andrej@1740:     "SINT":    0x02,
andrej@1740:     "INT":     0x03,
andrej@1740:     "DINT":    0x04,
andrej@1740:     "LINT":    0x10,
andrej@1740:     "USINT":   0x05,
andrej@1740:     "UINT":    0x06,
andrej@1740:     "UDINT":   0x07,
andrej@1740:     "ULINT":   0x1B,
andrej@1740:     "REAL":    0x08,
andrej@1740:     "LREAL":   0x11,
andrej@1740:     "STRING":  0x09,
andrej@1740:     "BYTE":    0x05,
andrej@1740:     "WORD":    0x06,
andrej@1740:     "DWORD":   0x07,
andrej@1740:     "LWORD":   0x1B,
andrej@1740:     "WSTRING": 0x0B
andrej@1740: }
etisserant@58: 
andrej@1730: # Constants for PDO types
etisserant@58: RPDO = 1
etisserant@58: TPDO = 2
etisserant@58: 
andrej@1739: SlavePDOType = {"I": TPDO, "Q": RPDO}
andrej@1739: InvertPDOType = {RPDO: TPDO, TPDO: RPDO}
andrej@1739: PDOTypeBaseIndex = {RPDO: 0x1400, TPDO: 0x1800}
andrej@1739: PDOTypeBaseCobId = {RPDO: 0x200, TPDO: 0x180}
etisserant@58: 
etisserant@58: VariableIncrement = 0x100
andrej@1739: VariableStartIndex = {TPDO: 0x2000, RPDO: 0x4000}
andrej@1739: VariableDirText = {TPDO: "__I", RPDO: "__Q"}
andrej@1740: 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: 
andrej@1782: # -------------------------------------------------------------------------------
greg@340: #                  Specific exception for PDO mapping errors
andrej@1782: # -------------------------------------------------------------------------------
greg@340: 
andrej@1736: 
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:     """
andrej@1730: 
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: 
andrej@1744: def GetNodePDOIndexes(node, type, parameters=False):
etisserant@58:     """
etisserant@58:     Find the PDO indexes of a node
andrej@1730:     @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:     """
andrej@1730: 
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
andrej@1730:     @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:     """
andrej@1730: 
lbessard@270:     model = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8)
andrej@1730: 
etisserant@58:     for PDOidx in GetNodePDOIndexes(node, loc_infos["pdotype"]):
etisserant@58:         values = node.GetEntry(PDOidx)
andrej@1743:         if values is not 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
andrej@1730:     @return: a tuple of value and number of parameters to add to DCF
andrej@1730:     """
andrej@1730: 
andrej@1742:     dcfdata = []
etisserant@58:     # Create entry for RPDO or TPDO parameters and Disable PDO
Edouard@1278:     #           ---- INDEX -----   --- SUBINDEX ----   ----- SIZE ------   ------ DATA ------
Edouard@1278:     dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)]
Edouard@1278:     # Set Transmit type
Edouard@1278:     dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)]
greg@340:     if len(pdomapping) > 0:
Edouard@1278:         # Disable Mapping
Edouard@1278:         dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(0x00, 1)]
greg@340:         # Map Variables
andrej@1847:         for subindex, (_name, loc_infos) in enumerate(pdomapping):
greg@340:             value = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8) + loc_infos["size"]
Edouard@1278:             dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)]
Edouard@1278:         # Re-enable Mapping
Edouard@1278:         dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)]
Edouard@1278:     # Re-Enable PDO
Edouard@1278:     dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)]
Edouard@1278:     return "".join(dcfdata), len(dcfdata)
etisserant@58: 
andrej@1736: 
andrej@1831: class ConciseDCFGenerator(object):
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 = {}
andrej@1730: 
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
andrej@1730: 
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:         """
andrej@1730: 
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)
andrej@1730: 
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
andrej@1739:             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)
andrej@1730: 
etisserant@58:         return len(nodeRpdoIndexes), len(nodeTpdoIndexes)
etisserant@58: 
etisserant@58:     def PrepareMasterNode(self):
etisserant@58:         """
etisserant@58:         Add mandatory entries for DCF generation into MasterNode.
etisserant@58:         """
andrej@1730: 
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)
andrej@1730: 
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
andrej@1730: 
etisserant@58:         RPDOnumber, TPDOnumber = self.RemoveUsedNodeCobId(self.MasterNode)
andrej@1730: 
etisserant@58:         # Store the indexes of the first RPDO and TPDO available for MasterNode
andrej@1739:         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)
andrej@1730: 
etisserant@58:             RPDOnumber, TPDOnumber = self.RemoveUsedNodeCobId(node)
andrej@1730: 
etisserant@58:             # Get Slave's default SDO server parameters
andrej@1740:             RSDO_cobid = node.GetEntry(0x1200, 0x01)
etisserant@58:             if not RSDO_cobid:
etisserant@58:                 RSDO_cobid = 0x600 + nodeid
andrej@1740:             TSDO_cobid = node.GetEntry(0x1200, 0x02)
etisserant@58:             if not TSDO_cobid:
etisserant@58:                 TSDO_cobid = 0x580 + nodeid
andrej@1730: 
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)
andrej@1730:             self.MasterNode.SetEntry(0x1280 + idx, 0x03, nodeid)
andrej@1730: 
etisserant@58:     def GetMasterNode(self):
etisserant@58:         """
etisserant@58:         Return MasterNode.
etisserant@58:         """
etisserant@58:         return self.MasterNode
andrej@1730: 
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)
andrej@1730: 
etisserant@58:         # Extract data and number of params in current DCF
andrej@1743:         if nodeDCF is not None and nodeDCF != '':
etisserant@58:             tmpnbparams = [i for i in nodeDCF[:4]]
etisserant@58:             tmpnbparams.reverse()
andrej@1734:             nbparams += int(''.join(["%2.2x" % ord(i) for i in tmpnbparams]), 16)
etisserant@58:             data = nodeDCF[4:] + data
andrej@1730: 
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)
andrej@1730: 
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
andrej@1730: 
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)
andrej@1743:             if values is not 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
andrej@1730: 
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
andrej@1768:         self.MasterMapping[pdocobid] = {
andrej@1768:             "type":    InvertPDOType[pdotype],
andrej@1768:             "mapping": [None] + [(loc_infos["type"], name) for name, loc_infos in pdomapping]
andrej@1768:         }
andrej@1730: 
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, ""
andrej@1730: 
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:         """
andrej@1730: 
andrej@1782:         # -------------------------------------------------------------------------------
etisserant@58:         #               Verify that locations correspond to real slave variables
andrej@1782:         # -------------------------------------------------------------------------------
andrej@1730: 
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:
andrej@1765:                     raise PDOmappingException(_("Type conflict 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):
andrej@1765:                     raise PDOmappingException(_("Bad location size : %s") % str(loc))
etisserant@166:                 elif len(loc) == 2:
etisserant@166:                     continue
andrej@1730: 
etisserant@58:                 direction = location["DIR"]
andrej@1730: 
etisserant@58:                 sizelocation = location["SIZE"]
andrej@1730: 
etisserant@58:                 # Extract and check nodeid
etisserant@58:                 nodeid, index, subindex = loc[:3]
andrej@1730: 
etisserant@58:                 # Check Id is in slave node list
etisserant@58:                 if nodeid not in self.NodeList.SlaveNodes.keys():
andrej@1765:                     raise PDOmappingException(
andrej@1765:                         _("Non existing node ID : {a1} (variable {a2})").
andrej@1765:                         format(a1=nodeid, a2=name))
andrej@1730: 
etisserant@58:                 # Get the model for this node (made from EDS)
etisserant@58:                 node = self.NodeList.SlaveNodes[nodeid]["Node"]
andrej@1730: 
etisserant@58:                 # Extract and check index and subindex
etisserant@58:                 if not node.IsEntry(index, subindex):
andrej@1581:                     msg = _("No such index/subindex ({a1},{a2}) in ID : {a3} (variable {a4})").\
andrej@1744:                           format(a1="%x" % index, a2="%x" % subindex, a3=nodeid, a4=name)
andrej@1765:                     raise PDOmappingException(msg)
andrej@1730: 
etisserant@58:                 # Get the entry info
etisserant@58:                 subentry_infos = node.GetSubentryInfos(index, subindex)
andrej@1730: 
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:
andrej@1765:                         raise PDOmappingException(
andrej@1765:                             _("Cannot set bit offset for non bool '{a1}' variable (ID:{a2},Idx:{a3},sIdx:{a4}))").
andrej@1765:                             format(a1=name, a2=nodeid, a3="%x" % index, a4="%x" % subindex))
etisserant@58:                     else:
etisserant@58:                         numbit = None
andrej@1730: 
etisserant@166:                     if location["IEC_TYPE"] != "BOOL" and subentry_infos["type"] != COlocationtype:
andrej@1765:                         raise PDOmappingException(
andrej@1765:                             _("Invalid type \"{a1}\"-> {a2} != {a3}  for location \"{a4}\"").
andrej@1765:                             format(a1=location["IEC_TYPE"],
andrej@1765:                                    a2=COlocationtype,
andrej@1765:                                    a3=subentry_infos["type"],
andrej@1765:                                    a4=name))
andrej@1730: 
etisserant@58:                     typeinfos = node.GetEntryInfos(COlocationtype)
andrej@1740:                     self.IECLocations[name] = {
andrej@1740:                         "type":         COlocationtype,
andrej@1740:                         "pdotype":      SlavePDOType[direction],
andrej@1740:                         "nodeid":       nodeid,
andrej@1740:                         "index":        index,
andrej@1740:                         "subindex":     subindex,
andrej@1740:                         "bit":          numbit,
andrej@1740:                         "size":         typeinfos["size"],
andrej@1740:                         "sizelocation": sizelocation
andrej@1740:                     }
etisserant@58:                 else:
andrej@1765:                     raise PDOmappingException(
andrej@1765:                         _("Not PDO mappable variable : '{a1}' (ID:{a2},Idx:{a3},sIdx:{a4}))").
andrej@1765:                         format(a1=name, a2=nodeid, a3="%x" % index, a4="%x" % subindex))
andrej@1730: 
andrej@1782:         # -------------------------------------------------------------------------------
etisserant@58:         #                         Search for locations already mapped
andrej@1782:         # -------------------------------------------------------------------------------
andrej@1730: 
etisserant@58:         for name, locationinfos in self.IECLocations.items():
etisserant@58:             node = self.NodeList.SlaveNodes[locationinfos["nodeid"]]["Node"]
andrej@1730: 
etisserant@58:             # Search if slave has a PDO mapping this locations
etisserant@58:             result = SearchNodePDOMapping(locationinfos, node)
andrej@1743:             if result is not 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)
andrej@1730: 
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, [])
andrej@1730: 
andrej@1730:                         # Add entry to slave dcf to change transmit type of
lbessard@80:                         self.AddParamsToDCF(locationinfos["nodeid"], data, nbparams)
andrej@1730: 
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)
andrej@1739:                     self.MasterMapping[cobid] = {"type": InvertPDOType[locationinfos["pdotype"]], "mapping": mapping}
andrej@1730: 
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)
andrej@1730: 
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():
andrej@1739:                     self.LocationsNotMapped[locationinfos["nodeid"]] = {TPDO: [], RPDO: []}
etisserant@58:                 self.LocationsNotMapped[locationinfos["nodeid"]][locationinfos["pdotype"]].append((name, locationinfos))
andrej@1730: 
andrej@1782:         # -------------------------------------------------------------------------------
etisserant@58:         #                         Build concise DCF for the others locations
andrej@1782:         # -------------------------------------------------------------------------------
andrej@1730: 
etisserant@58:         for nodeid, locations in self.LocationsNotMapped.items():
lbessard@61:             node = self.NodeList.SlaveNodes[nodeid]["Node"]
andrej@1730: 
etisserant@58:             # Initialize number of params and data to add to node DCF
etisserant@58:             nbparams = 0
etisserant@58:             dataparams = ""
andrej@1730: 
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:
andrej@1765:                         raise PDOmappingException(
andrej@1765:                             _("Unable 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:
andrej@1765:                                 raise PDOmappingException(
andrej@1765:                                     _("Unable 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
andrej@1730: 
etisserant@58:             # Add number of params and data to node DCF
etisserant@58:             self.AddParamsToDCF(nodeid, dataparams, nbparams)
andrej@1730: 
andrej@1782:         # -------------------------------------------------------------------------------
etisserant@58:         #                         Master Node Configuration
andrej@1782:         # -------------------------------------------------------------------------------
andrej@1730: 
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"]]
andrej@1730: 
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
andrej@1730: 
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)
andrej@1730: 
etisserant@58:                 # Increment the number of PDO for this PDO type
etisserant@58:                 self.CurrentPDOParamsIdx[pdo_infos["type"]] += 1
andrej@1730: 
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)
andrej@1730: 
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)
andrej@1730: 
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)
andrej@1730: 
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
andrej@1730: 
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]
andrej@1730: 
etisserant@58:                     # Calculate base index for storing variable
andrej@1767:                     mapvariableidx = \
andrej@1767:                         VariableStartIndex[variable_infos["pdotype"]] + \
andrej@1767:                         VariableTypeOffset[variable_infos["sizelocation"]] * VariableIncrement + \
andrej@1767:                         variable_infos["nodeid"]
andrej@1730: 
etisserant@163:                     # Generate entry name
andrej@1734:                     indexname = "%s%s%s_%d" % (VariableDirText[variable_infos["pdotype"]],
andrej@1767:                                                variable_infos["sizelocation"],
andrej@1767:                                                '_'.join(map(str, current_location)),
andrej@1767:                                                variable_infos["nodeid"])
andrej@1730: 
andrej@1730:                     # Search for an entry that has an empty subindex
etisserant@58:                     while mapvariableidx < VariableStartIndex[variable_infos["pdotype"]] + 0x2000:
etisserant@58:                         # Entry doesn't exist
andrej@1730:                         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
andrej@1730: 
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
andrej@1743:                         if variable_infos["bit"] is not None:
andrej@1734:                             subindexname = "%(index)d_%(subindex)d_%(bit)d" % variable_infos
etisserant@58:                         else:
andrej@1734:                             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
andrej@1744:                         self.MasterNode.SetMappingEntry(mapvariableidx, nbsubentries, values={"name": subindexname})
andrej@1744:                         self.MasterNode.SetMappingEntry(mapvariableidx, nbsubentries, values={"type": typeidx})
andrej@1730: 
etisserant@58:                         # Set value of the PDO mapping
etisserant@58:                         typeinfos = self.Manager.GetEntryInfos(typeidx)
andrej@1743:                         if typeinfos is not None:
etisserant@58:                             value = (mapvariableidx << 16) + ((nbsubentries) << 8) + typeinfos["size"]
etisserant@58:                             self.MasterNode.SetEntry(current_idx + 0x200, subindex, value)
andrej@1730: 
etisserant@163:                         # Add variable to pointed variables
andrej@1734:                         self.PointedVariables[(mapvariableidx, nbsubentries)] = "%s_%s" % (indexname, subindexname)
etisserant@58: 
andrej@1736: 
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:     """
andrej@1730: 
etisserant@58:     dcfgenerator = ConciseDCFGenerator(nodelist, nodename)
etisserant@58:     dcfgenerator.GenerateDCF(locations, current_location, sync_TPDOs)
andrej@1740:     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))
andrej@1740:     return masternode, pointers
etisserant@58: 
andrej@1736: 
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:
andrej@1765:                 raise PDOmappingException(_("Type conflict 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):
andrej@1765:                 raise PDOmappingException(_("Bad location size : %s") % str(loc))
etisserant@166:             elif len(loc) != 2:
etisserant@166:                 continue
andrej@1730: 
etisserant@166:             # Extract and check nodeid
etisserant@166:             index, subindex = loc[:2]
andrej@1730: 
etisserant@166:             # Extract and check index and subindex
etisserant@166:             if not slave.IsEntry(index, subindex):
andrej@1765:                 raise PDOmappingException(
andrej@1765:                     _("No such index/subindex ({a1},{a2}) (variable {a3})").
andrej@1765:                     format(a1="%x" % index, a2="%x" % subindex, a3=name))
andrej@1730: 
etisserant@166:             # Get the entry info
andrej@1730:             subentry_infos = slave.GetSubentryInfos(index, subindex)
etisserant@166:             if subentry_infos["type"] != COlocationtype:
andrej@1765:                 raise PDOmappingException(
andrej@1765:                     _("Invalid type \"{a1}\"-> {a2} != {a3} for location \"{a4}\"").
andrej@1765:                     format(a1=location["IEC_TYPE"],
andrej@1765:                            a2=COlocationtype,
andrej@1765:                            a3=subentry_infos["type"],
andrej@1765:                            a4=name))
andrej@1730: 
etisserant@166:             IECLocations[name] = COlocationtype
etisserant@166:             pointers[(index, subindex)] = name
etisserant@166:     return pointers
andrej@1730: 
andrej@1749: 
etisserant@58: if __name__ == "__main__":
etisserant@58:     def usage():
andrej@1826:         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.
andrej@1826: """ % sys.argv[0])
andrej@1730: 
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:
andrej@1740:         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"))
andrej@1730: 
etisserant@58:     from nodemanager import *
etisserant@58:     from nodelist import *
andrej@1730: 
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")
andrej@1730: 
etisserant@58:     # List of locations, we try to map for test
andrej@1740:     locations = [
andrej@1740:         {"IEC_TYPE": "BYTE",  "NAME": "__IB0_1_64_24576_1", "DIR": "I", "SIZE": "B", "LOC": (0, 1, 64, 24576, 1)},
andrej@1740:         {"IEC_TYPE": "INT",   "NAME": "__IW0_1_64_25601_2", "DIR": "I", "SIZE": "W", "LOC": (0, 1, 64, 25601, 2)},
andrej@1740:         {"IEC_TYPE": "INT",   "NAME": "__IW0_1_64_25601_3", "DIR": "I", "SIZE": "W", "LOC": (0, 1, 64, 25601, 3)},
andrej@1740:         {"IEC_TYPE": "INT",   "NAME": "__QW0_1_64_25617_2", "DIR": "Q", "SIZE": "W", "LOC": (0, 1, 64, 25617, 1)},
andrej@1740:         {"IEC_TYPE": "BYTE",  "NAME": "__IB0_1_64_24578_1", "DIR": "I", "SIZE": "B", "LOC": (0, 1, 64, 24578, 1)},
andrej@1740:         {"IEC_TYPE": "UDINT", "NAME": "__ID0_1_64_25638_1", "DIR": "I", "SIZE": "D", "LOC": (0, 1, 64, 25638, 1)},
andrej@1740:         {"IEC_TYPE": "UDINT", "NAME": "__ID0_1_64_25638_2", "DIR": "I", "SIZE": "D", "LOC": (0, 1, 64, 25638, 2)},
andrej@1740:         {"IEC_TYPE": "UDINT", "NAME": "__ID0_1_64_25638_3", "DIR": "I", "SIZE": "D", "LOC": (0, 1, 64, 25638, 3)},
andrej@1740:         {"IEC_TYPE": "UDINT", "NAME": "__ID0_1_64_25638_4", "DIR": "I", "SIZE": "D", "LOC": (0, 1, 64, 25638, 4)},
andrej@1740:         {"IEC_TYPE": "UDINT", "NAME": "__ID0_1_4096_0",     "DIR": "I", "SIZE": "D", "LOC": (0, 1, 4096, 0)}
andrej@1740:     ]
andrej@1730: 
etisserant@58:     # Generate MasterNode configuration
etisserant@58:     try:
etisserant@307:         masternode, pointedvariables = GenerateConciseDCF(locations, (0, 1), nodelist, True, "TestNode")
etisserant@58:     except ValueError, message:
andrej@1826:         print("%s\nTest Failed!" % message)
etisserant@58:         sys.exit()
andrej@1730: 
etisserant@307:     import pprint
andrej@1730:     # 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"
andrej@1730: 
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()
andrej@1730: 
andrej@1826:         print("Reset Successful!")
etisserant@58:     else:
etisserant@307:         testfile = open("test_config/result_tmp.txt", "w")
etisserant@307:         testfile.write(result)
etisserant@307:         testfile.close()
andrej@1730: 
etisserant@150:         os.system("diff test_config/result.txt test_config/result_tmp.txt")
etisserant@150:         os.remove("test_config/result_tmp.txt")