/** * SOES EEPROM generator * Object Dictionary edition logic * This tool serves as: - EtherCAT Slave Information XML + EEPROM binary generator - SOES code generator * Victor Sluiter 2013-2018 * Kuba Buda 2020-2021 */ 'use strict' /** Object Dictionary sections edited by UI * Assumption: single non dynamic PDO */ const _odSections = { sdo : {}, txpdo : {}, // addding PDO requires matching SDO in Sync Manager, and PDO mapping rxpdo : {}, // this will be done when stitching sections during code generation }; function getObjDict() { return _odSections; } function getObjDictSection(odSectionName) { return _odSections[odSectionName]; } function setObjDictSection(odSectionName, backupValue) { _odSections[odSectionName] = backupValue; } function objectExists(odSectionName, index) { var odSection = getObjDictSection(odSectionName); return index && odSection[index]; } function checkObjectType(expected, objd) { if (objd.otype != expected) { var msg = `Object ${objd.name} was expected to be OTYPE ${expected} but is ${objd.otype}`; alert(msg); throw new Exception(msg); } } function addObject(od, objd, index) { if (od[index]) { alert(`Object ${objd.name} duplicates 0x${index}: ${od[index].name} !`); } od[index] = objd; } function removeObject(od, index) { if (index) { if (od[index]) { delete od[index]; } else { alert(`Cannot remove object 0x${index}: it does not exist`); } } } function isInArray(array, seekValue) { return array && (array[0] == seekValue || array.find(currentValue => currentValue == seekValue)); } function variableName(objectName) { const charsToReplace = [ ' ', '.', ',', ';', ':', '/' ]; const charsToRemove = [ '+', '-', '*', '=', '!', '@' ]; var variableName = objectName; charsToReplace.forEach(c => { variableName = variableName.replaceAll(c, '_'); }); charsToRemove.forEach(c => { variableName = variableName.replaceAll(c, ''); }); return variableName; } // ####################### Building Object Dictionary model ####################### // /** Takes OD entries from UI SDO section and adds to given OD */ function addSDOitems(od) { const sdoSection = getObjDictSection(sdo); const indexes = getUsedIndexes(sdoSection); indexes.forEach(index => { const item = sdoSection[index]; item.isSDOitem = true; objectlist_link_utypes(item); addObject(od, item, index); }); } /** Returns true if any object in given Object Dictionary has mapping to PDO with given name */ function isPdoWithVariables(od, indexes, pdoName) { for (let i = 0; i < indexes.length; i++) { const index = indexes[i]; const objd = od[index]; if (isInArray(objd.pdo_mappings, pdoName)) { return true; } } return false; } /** Regardles of value set, SDK was generating RXPDO mappings as SDO1600 * This offset _can_ be changed, not sure why one would need it */ function getSM2_MappingOffset(form) { return parseInt(form.SM2Offset.value); } /** Takes OD entries from UI RXPDO section and adds to given OD */ function addRXPDOitems(od) { const rxpdoSection = getObjDictSection(rxpdo); const form = getForm(); const pdo = { name : rxpdo, SMassignmentIndex : '1C12', smOffset : getSM2_MappingOffset(form), // usually 0x1600 }; addPdoObjectsSection(od, rxpdoSection, pdo); } /** Takes OD entries from UI TXPDO section and adds to given OD */ function addTXPDOitems(od) { const txpdoSection = getObjDictSection(txpdo); const form = getForm(); const pdo = { name : txpdo, SMassignmentIndex : '1C13', smOffset : parseInt(form.SM3Offset.value), // usually 0x1A00 }; addPdoObjectsSection(od, txpdoSection, pdo); } var _booleanPaddingCount = 0; /** * Takes OD entries from given UI SDO/PDO section and adds to given OD * using provided SM offset, and SM assignment address. * Available sections are 'sdo', 'txpdo', 'rxpdo' */ function addPdoObjectsSection(od, odSection, pdo){ var currentSMoffsetValue = pdo.smOffset; const indexes = getUsedIndexes(odSection); if (indexes.length) { const pdoAssignments = ensurePDOAssignmentExists(od, pdo.SMassignmentIndex); indexes.forEach(index => { const objd = odSection[index]; const currentOffset = indexToString(currentSMoffsetValue) const pdoMappingObj = { otype: OTYPE.RECORD, name: objd.name, items: [ { name: 'Max SubIndex' }, ]}; // create PDO assignment to SM const pdoAssignment = { name: "PDO Mapping", value: `0x${currentOffset}` }; addPdoMapping(objd, pdo.name); objectlist_link_utypes(objd); switch (objd.otype) { case OTYPE.VAR: { // create PDO mapping pdoMappingObj.items.push({ name: objd.name, dtype: DTYPE.UNSIGNED32, value: getPdoMappingValue(index, 0, objd.dtype) }); if (objd.dtype == DTYPE.BOOLEAN) { addBooleanPadding(pdoMappingObj.items, ++_booleanPaddingCount); } break; } case OTYPE.ARRAY: { var subindex = 1; objd.items.slice(subindex).forEach(subitem => { // create PDO mappings pdoMappingObj.items.push({ name: subitem.name, dtype: DTYPE.UNSIGNED32, value: getPdoMappingValue(index, subindex , objd.dtype) }); // TODO handle padding on array of booleans ++subindex; }); break; } case OTYPE.RECORD: { var subindex = 1; objd.items.slice(subindex).forEach(subitem => { // create PDO mappings pdoMappingObj.items.push({ name: subitem.name, dtype: DTYPE.UNSIGNED32, value: getPdoMappingValue(index, subindex , subitem.dtype) }); if (subitem.dtype == DTYPE.BOOLEAN) { addBooleanPadding(pdoMappingObj.items, ++_booleanPaddingCount); } ++subindex; }); break; } default: { alert(`${pdoMappingValue} object ${index} ${objd.name} has unexpected object type ${objd.otype}!`); break; }} addObject(od, pdoMappingObj, currentOffset); pdoAssignments.items.push(pdoAssignment); addObject(od, objd, index); ++currentSMoffsetValue; }); function addBooleanPadding(mappingOjbItems, paddingCount) { mappingOjbItems.push({ name: `Padding ${paddingCount}`, dtype: DTYPE.UNSIGNED32, value: `0x0000000${booleanPaddingBitsize}` }); } } function addPdoMapping(objd, pdoName) { // make sure there is space if (!objd.pdo_mappings) { objd.pdo_mappings = []; } // mark object as PDO mapped, if it is not already if(!isInArray(objd.pdo_mappings, pdoName)) { objd.pdo_mappings.push(pdoName); } } function ensurePDOAssignmentExists(od, index) { var pdoAssignments = od[index]; if (!pdoAssignments) { pdoAssignments = { otype: OTYPE.ARRAY, dtype: DTYPE.UNSIGNED16, name: `Sync Manager ${index[3]} PDO Assignment`, items: [ { name: 'Max SubIndex' }, ]}; od[index] = pdoAssignments; } return pdoAssignments; } function getPdoMappingValue(index, subindex, dtype) { function toByte(value) { var result = value.toString(16).slice(0, 2); while (result.length < 2) { result = `0${result}`; } return result; } var bitsize = esiDTbitsize(dtype); return `0x${index}${toByte(subindex)}${toByte(bitsize)}`; } } /** populates mandatory objects with values from UI */ function populateMandatoryObjectValues(form, od) { if (form) { od['1008'].data = form.TextDeviceName.value; od['1009'].data = form.HWversion.value; od['100A'].data = form.SWversion.value; od['1018'].items[1].value = parseInt(form.VendorID.value); od['1018'].items[2].value = parseInt(form.ProductCode.value); od['1018'].items[3].value = parseInt(form.RevisionNumber.value); od['1018'].items[4].value = parseInt(form.SerialNumber.value); } } /** builds complete object dictionary, with values from UI */ function buildObjectDictionary(form) { const od = getMandatoryObjects(); populateMandatoryObjectValues(form, od); // populate custom objects addSDOitems(od); addTXPDOitems(od); addRXPDOitems(od); _booleanPaddingCount = 0; return od; } // ####################### Object Dictionary index manipulation ####################### // function indexToString(index) { var indexValue = parseInt(index); return indexValue.toString(16).toUpperCase(); } /** returns list of indexes that are used in given OD, as array of integer values */ function getUsedIndexes(od) { const index_min = 0x1000; const index_max = 0xFFFF; const usedIndexes = []; // scan index address space for ones used for (let i = index_min; i <= index_max; i++) { const index = indexToString(i); const element = od[index]; if (element) { usedIndexes.push(index); } } return usedIndexes; } // ####################### Object Dictionary edition ####################### // function getFirstFreeIndex(odSectionName) { var addressRangeStart = { "sdo": 0x2000, "txpdo": 0x6000, "rxpdo": 0x7000, } var result = addressRangeStart[odSectionName]; var odSection = getObjDictSection(odSectionName); while (odSection[indexToString(result)]) { result++; } return indexToString(result); } /** returns new object description for given PDO section */ function getNewObjd(odSectionName, otype) { const readableNames = { VAR: 'Variable', ARRAY: 'Array', RECORD: 'Record' } const objd = { otype: otype, name: `New ${readableNames[otype]}`, access: 'RO', }; switch(otype) { case OTYPE.ARRAY: { objd.items = [ { name: 'Max SubIndex' }, ]; addArraySubitem(objd); break; } case OTYPE.RECORD: { objd.items = [ { name: 'Max SubIndex' }, ]; addRecordSubitem(objd); break; }} if (odSectionName == txpdo || odSectionName == rxpdo) { objd.pdo_mappings = [ odSectionName ]; } return objd; } function addArraySubitem(objd) { if (objd.otype != OTYPE.ARRAY) { alert(`${objd} is not ARRAY, cannot add subitem`); return; } if (!objd.items) { alert(`${objd} does not have items list, cannot add subitem`); return; } const newSubitem = { name: 'New array subitem' } objd.items.push(newSubitem); return newSubitem; } function addRecordSubitem(objd) { if (objd.otype != OTYPE.RECORD) { alert(`${objd} is not RECORD, cannot add subitem`); return; } if (!objd.items) { alert(`${objd} does not have items list, cannot add subitem`); return; } const default_subitemDT = DTYPE.UNSIGNED8; // first from list const newSubitem = { name: 'New record subitem', dtype: default_subitemDT } objd.items.push(newSubitem); return newSubitem; }