/** * SOES EEPROM generator * ESI XML code generation 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' // ####################### ESI.xml generating ####################### // function esiDTbitsize(dtype) { return ESI_DT[dtype].bitsize; } //See ETG2000 for ESI format function esi_generator(form, od, indexes, dc) { //VendorID var esi =`\n\n \n ${parseInt(form.VendorID.value).toString()}\n`; //VendorName esi += ` ${form.VendorName.value}\n \n \n`; //Groups esi += ` \n \n ${form.TextGroupType.value}\n ${form.TextGroupName5.value}\n \n \n \n`; //Physics esi += ` \n ${form.TextDeviceType.value}\n`; //Add Name info esi += ` ${form.TextDeviceName.value}\n`; //Add in between esi += ` ${form.TextGroupType.value}\n`; //Add profile esi += ` \n ${form.ProfileNo.value}\n 0\n \n `; const customTypes = {}; const variableTypes = {}; function addVariableType(element) { if (element && element.otype && (element.otype != OTYPE.VAR && element.otype != OTYPE.ARRAY)) { alert(`${element.name} is not OTYPE VAR, cannot treat is as variable type`); return; } if (!element || !element.dtype) { alert(`${element.name} has no DTYPE, cannot treat is as variable type`); return; } let el_name = esiVariableTypeName(element); if (!variableTypes[el_name]) { const bitsize = (element.dtype == DTYPE.VISIBLE_STRING) ? esiBitsize(element) : esiDTbitsize(element.dtype); variableTypes[el_name] = bitsize; } } function addObjectDictionaryDataType(od, index) { const objd = od[index]; const dtName = esiDtName(objd, index); var result = ''; if (objd.otype == OTYPE.VAR) { addVariableType(objd); // variable types will have to be be done later anyway, add to that queue } else if (!customTypes[dtName]) { // generate data types code for complex objects const bitsize = esiBitsize(objd); customTypes[dtName] = true; result += `\n `; let flags = `\n ro`; // PDO assign flags for variables are set in dictionary objects section if (objd.otype == OTYPE.ARRAY) { addVariableType(objd); // queue variable type to add after array code is generated let esi_type = ESI_DT[objd.dtype]; let arr_bitsize = (objd.items.length - 1) * esi_type.bitsize result += `\n ${dtName}ARR\n ${esi_type.name}\n ${arr_bitsize}`; result += `\n \n 1\n ${objd.items.length - 1}\n `; result += `\n `; result += `\n `; } result += `\n ${dtName}\n ${bitsize}`; result += `\n \n 0\n Max SubIndex\n USINT` + `\n 8\n 0\n ${flags}\n \n `; flags += getPdoMappingFlags(objd); // PDO assign flags for composite type switch (objd.otype) { case OTYPE.ARRAY: { let arr_bitsize = (objd.items.length - 1) * esiDTbitsize(objd.dtype); result += `\n \n Elements\n ${dtName}ARR\n ${arr_bitsize}` +`\n 16\n ${flags}\n \n `; break; } case OTYPE.RECORD: { let subindex = 0; let bits_offset = 16; objd.items.forEach(subitem => { if (subindex > 0) { // skipped Max Subindex addVariableType(subitem); // cannot add variable type now that record code is being generated let subitem_dtype = ESI_DT[subitem.dtype]; let subitem_bitsize = subitem_dtype.bitsize const subitemFlags = getSubitemFlags(objd, subitem); result += `\n \n ${subindex}\n ${subitem.name}` + `\n ${subitem_dtype.name}\n ${subitem_bitsize}\n ${bits_offset}` + `\n ${subitemFlags}\n ` + `\n `; bits_offset += subitem_bitsize; } subindex++; }); break; } default: { alert(`Object ${index} "${objd.name}" has unexpected OTYPE ${objd.otype}`); alert; }} result += `\n `; } return result; function getSubitemFlags(objd, subitem) { let access = 'ro'; let modifier = ''; if (subitem.access) { access = subitem.access.slice(0,2).toLowerCase(); modifier = ' WriteRestrictions="PreOP"'; } let flags = `\n ${access}`; // PDO assign flags for variables are set in dictionary objects section flags += getPdoMappingFlags(objd); // PDO assign flags for composite type return flags; } } // Add objects dictionary data types indexes.forEach(index => { esi += addObjectDictionaryDataType(od, index); }); // Add variable type Object.entries(variableTypes).forEach(variableType => { esi += `\n `; esi += `\n ${variableType[0]}\n ${variableType[1]}`; esi += `\n `; }); esi += `\n \n `; // Add objects dictionary function addDictionaryObject(od, index) { const objd = od[index]; const el_dtype = esiDtName(objd, index); const bitsize = esiBitsize(objd); let result = `\n \n #x${index}\n ${objd.name}\n ${el_dtype}\n ${bitsize}\n `; if (objd.data) { if (objd.dtype == DTYPE.VISIBLE_STRING) { result += `\n ${objd.data}`; } } if (objd.value) { result += `\n ${toEsiHexValue(objd.value)}`; } //Add object subitems for complex types if (objd.items) { result += addDictionaryObjectSubitems(objd.items); } var flags = `\n ro`; if (objd.otype == OTYPE.VAR) { flags += getPdoMappingFlags(objd); } if (SDO_category[index]) { flags += `\n ${SDO_category[index]}`; } result += `\n \n ${flags}\n \n `; return result; function addDictionaryObjectSubitems(element_items) { const max_subindex_value = element_items.length - 1; var result = "" let subindex = 0; element_items.forEach(subitem => { var defaultValue = (subindex > 0) ? subitem.value : max_subindex_value; result += `\n \n ${subitem.name}\n \n ${toEsiHexValue(defaultValue)}\n \n `; subindex++; }); return result; } } indexes.forEach(index => { esi += addDictionaryObject(od, index); }); const is_rxpdo = isPdoWithVariables(od, indexes, rxpdo); const is_txpdo = isPdoWithVariables(od, indexes, txpdo); esi += `\n \n \n \n Outputs\n Inputs\n MBoxState\n`; //Add Rxmailbox sizes esi += ` MBoxOut\n`; //Add Txmailbox sizes esi += ` MBoxIn\n`; //Add SM2 esi += ` Outputs\n`; //Add SM3 esi += ` Inputs\n`; if (is_rxpdo) { var memOffset = getSM2_MappingOffset(form); indexes.forEach(index => { const objd = od[index]; if (isInArray(objd.pdo_mappings, rxpdo)) { esi += addEsiDevicePDO(objd, index, rxpdo, memOffset); ++memOffset; } }); } if (is_txpdo) { var memOffset = form.SM3Offset.value; indexes.forEach(index => { const objd = od[index]; if (isInArray(objd.pdo_mappings, txpdo)) { esi += addEsiDevicePDO(objd, index, txpdo, memOffset); ++memOffset; } }); } //Add Mailbox DLL esi += ` \n \n \n`; //Add DCs esi += getEsiDCsection(dc); //Add EEPROM const configdata = hex_generator(form, true); esi +=` \n ${parseInt(form.EEPROMsize.value)}\n ${configdata}\n \n`; //Close all items esi +=` \n \n \n`; return esi; function addEsiDevicePDO(objd, index, pdo, memOffset) { var esi = ''; const PdoName = pdo[0].toUpperCase(); const SmNo = (pdo == txpdo) ? 3 : 2; const memoryOffset = indexToString(memOffset); esi += ` <${PdoName}xPdo Fixed="true" Mandatory="true" Sm="${SmNo}">\n #x${memoryOffset}\n ${objd.name}`; var subindex = 0; switch (objd.otype) { case OTYPE.VAR: { const esiType = esiVariableTypeName(objd); const bitsize = esiDTbitsize(objd.dtype); esi += `\n \n #x${index}\n #x${subindex.toString(16)}\n ${bitsize}\n ${objd.name}\n ${esiType}\n `; esi += pdoBooleanPadding(objd); break; } case OTYPE.ARRAY: { const esiType = esiVariableTypeName(objd); const bitsize = esiDTbitsize(objd.dtype); subindex = 1; // skip 'Max subindex' objd.items.slice(subindex).forEach(subitem => { esi += `\n \n #x${index}\n #x${subindex.toString(16)}\n ${bitsize}\n ${subitem.name}\n ${esiType}\n `; // TODO handle padding for array of booleans ++subindex; }); break; } case OTYPE.RECORD: { subindex = 1; // skip 'Max subindex' objd.items.slice(subindex).forEach(subitem => { const esiType = esiVariableTypeName(subitem); const bitsize = esiDTbitsize(subitem.dtype); esi += `\n \n #x${index}\n #x${subindex.toString(16)}\n ${bitsize}\n ${subitem.name}\n ${esiType}\n `; esi += pdoBooleanPadding(subitem); ++subindex; }); break; } default: { alert(`Unexpected OTYPE ${objd.otype} for ${index} ${objd.name} in ESI ${PdoName}PDOs`); break; }} esi += `\n \n`; return esi; function pdoBooleanPadding(item) { if (item.dtype == DTYPE.BOOLEAN) { return `\n \n ${0}\n ${0}\n ${7}\n `; } return ``; } } function toEsiHexValue(value) { if (!value) { return 0; } if (value.startsWith && value.startsWith('0x')) { value = `#x${value.slice(2)}`; } return value; } function getPdoMappingFlags(item) { var flags = ''; if (item.pdo_mappings) { if (item.pdo_mappings.length > 1) { alert(`Object ${index} "${objd.name}" has multiple PDO mappings, that is not supported by this version of tool` + `, only first ${pdoMappingFlag}XPDO will be used`); } const pdoMappingFlag = item.pdo_mappings[0].slice(0,1).toUpperCase(); flags += `\n ${pdoMappingFlag}`; } return flags; } function getEsiDCsection(dc) { if (!dc) { return ''; } var dcSection = ' '; dc.forEach(opMode => { dcSection += `\n \n ${opMode.Name}\n ${opMode.Description}\n ${opMode.AssignActivate}`; if (opMode.Sync0cycleTime && opMode.Sync0cycleTime != 0) { dcSection += `\n ${opMode.Sync0cycleTime}`; } if (opMode.Sync0shiftTime && opMode.Sync0shiftTime != 0) { dcSection += `\n ${opMode.Sync0shiftTime}`; } if (opMode.Sync1cycleTime && opMode.Sync1cycleTime != 0) { dcSection += `\n ${opMode.Sync1cycleTime}`; } if (opMode.Sync1shiftTime && opMode.Sync1shiftTime != 0) { dcSection += `\n ${opMode.Sync1shiftTime}`; } dcSection += `\n `; }); dcSection += `\n \n`; return dcSection; } //See Table 40 ETG2000 function getCoEString(form) { var result = "" // if(form.CoeDetailsEnableSDO.checked) // result += 'SdoInfo="true" '; // else // result += 'SdoInfo="false" '; if(form.CoeDetailsEnableSDOInfo.checked) result += 'SdoInfo="true" '; else result += 'SdoInfo="false" '; if(form.CoeDetailsEnablePDOAssign.checked) result += 'PdoAssign="true" '; else result += 'PdoAssign="false" '; if(form.CoeDetailsEnablePDOConfiguration.checked) result += 'PdoConfig="true" '; else result += 'PdoConfig="false" '; if(form.CoeDetailsEnableUploadAtStartup.checked) result += 'PdoUpload="true" '; else result += 'PdoUpload="false" '; if(form.CoeDetailsEnableSDOCompleteAccess.checked) result += 'CompleteAccess="true" '; else result +='CompleteAccess="false" '; return result; } function esiVariableTypeName(element) { let el_name = ESI_DT[element.dtype].name; if (element.dtype == DTYPE.VISIBLE_STRING) { return `${el_name}(${element.data.length})`; } return el_name; } function esiDtName(element, index) { switch (element.otype) { case OTYPE.VAR: return esiVariableTypeName(element); case OTYPE.ARRAY: case OTYPE.RECORD: return `DT${index}`; default: alert(`Element 0x${index} has unexpected OTYPE ${element.otype}`); break; } } function esiBitsize(element) { switch (element.otype) { case OTYPE.VAR: { let bitsize = esiDTbitsize(element.dtype); if (element.dtype == DTYPE.VISIBLE_STRING) { return bitsize * element.data.length; } return bitsize; } case OTYPE.ARRAY: { const maxsubindex_bitsize = esiDTbitsize(DTYPE.UNSIGNED8); let bitsize = esiDTbitsize(element.dtype); let elements = element.items.length - 1; // skip max subindex return maxsubindex_bitsize * 2 + elements * bitsize; } case OTYPE.RECORD: { const maxsubindex_bitsize = esiDTbitsize(DTYPE.UNSIGNED8); let bitsize = maxsubindex_bitsize * 2; for (let subindex = 1; subindex < element.items.length; subindex++) { const subitem = element.items[subindex]; bitsize += esiDTbitsize(subitem.dtype); if(subitem.dtype == DTYPE.BOOLEAN) { bitsize += booleanPaddingBitsize; } } return bitsize; } default: alert(`Element ${element} has unexpected OTYPE ${element.otype}`); break; } } }