Reshuffle file structure
This commit is contained in:
363
EEPROM_generator/src/od.js
Normal file
363
EEPROM_generator/src/od.js
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user