Think I got the EEPROM_Generator right

This commit is contained in:
Hakan Bastedt
2023-12-22 22:26:40 +01:00
parent 5bb330f215
commit c972fef18f
29 changed files with 15546 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
*.bin
node_modules

View File

@@ -0,0 +1,26 @@
EEPROM generator
Victor Sluiter 2013-2018
Kuba Buda 2020-2022
EEPROM generator is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License version 2 as published by the Free
Software Foundation.
EEPROM generator is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
As a special exception, if other files instantiate templates or use macros
or inline functions from this file, or you compile this file and link it
with other works to produce a work based on this file, this file does not
by itself cause the resulting work to be covered by the GNU General Public
License. However the source code for this file must still be made available
in accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work based on
this file might be covered by the GNU General Public License.
The EtherCAT Technology, the trade name and logo "EtherCAT" are the intellectual
property of, and protected by Beckhoff Automation GmbH.

View File

@@ -0,0 +1,120 @@
# [🔁 EEPROM generator](https://kubabuda.github.io/EEPROM_generator)
This is basic code generator tool for EtherCAT devices, using [SOES library](https://github.com/OpenEtherCATsociety/SOES).
[It is available online, here](https://kubabuda.github.io/EEPROM_generator)
You can configure:
- ESC (Ethercat Slave Chip)
- OD (CANopen Object Dictionary) entries
- PDO mappings (which OD objects are mapped in TX, RX datagrams)
Tool generates consistent data across C sources, ESI file and EEPROM content.
It also backs up your current project in localstorage. You can save project to JSON file on your hard drive, restore from it later, and download all files at once.
## Limitations
- Only single, non-dynamic PDO is supported for TX and RX respectively
- Some data types might not be supported
- Browsers other than Firefox might not be supported
If you need more, [RT-Labs](https://rt-labs.com/ethercat/) offers professional IDE - EtherCAT SDK, and training.
# Development
Pull requests welcome.
Source code is intentionally keept in plain Javascript files so that build system like webpack or even web server is not needed.
The only dependency is web browser, that should future proof it.
## [Unit tests](https://kubabuda.github.io/EEPROM_generator/tests.html)
[Unit tests](https://kubabuda.github.io/EEPROM_generator/tests.html) are using [Jasmine](https://jasmine.github.io).
## OD structure
OD is keept as JSON object. Expected data format:
```js
{
'1000': { otype: OTYPE.VAR, dtype: DTYPE.UNSIGNED32, name: 'Device Type', value: 0x1389 },
'1008': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Device Name', data: '' },
'1009': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Hardware Version', data: '' },
'100A': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Software Version', data: '' },
'1018': { otype: OTYPE.RECORD, name: 'Identity Object', items: [
{ name: 'Max SubIndex' },
{ name: 'Vendor ID', dtype: DTYPE.UNSIGNED32, value: 600 },
{ name: 'Product Code', dtype: DTYPE.UNSIGNED32 },
{ name: 'Revision Number', dtype: DTYPE.UNSIGNED32 },
{ name: 'Serial Number', dtype: DTYPE.UNSIGNED32, data: '&Obj.serial' },
]},
'1C00': { otype: OTYPE.ARRAY, dtype: DTYPE.UNSIGNED8, name: 'Sync Manager Communication Type', items: [
{ name: 'Max SubIndex' },
{ name: 'Communications Type SM0', value: 1 },
{ name: 'Communications Type SM1', value: 2 },
{ name: 'Communications Type SM2', value: 3 },
{ name: 'Communications Type SM3', value: 4 },
]},
}
```
OD model for generator has 4 sections:
- `sdo`, not mapped to PDOs
- `txpdo`, mapped to TXPDO (SM3). Expected format (for OTYPE VAR):
```js
{
'6000': { otype: OTYPE.VAR, dtype: DTYPE.UNSIGNED32, name: 'TXPDO', value: 0x1389, pdo_mappings: ['tx'] },
}
```
- `rxpdo`, same as above, but `pdo_mappings: ['rx']`
- mandatory objects. These are added at code gen stage, with values populated form UI controls.
Code generation copies all values into single OD, adds PDO mappings and SM assignments.
## PDO mappings
Currently single, non-dynamic PDO is supported for TX and RX respectively.
## Binary file comparison tool for Windows: [VBinDiff](https://www.cjmweb.net/vbindiff/VBinDiff-Win32)
```cmd
VBinDiff et1100.bin lan9252.bin
```
# TODO
- add support for 64bit number types
- why default dark mode has dark text in Chromium but not Firefox, when OS is in dark mode
- generate .h file for EEPROM emulation
- add support for LAN9253 Direct, XMC4300, XMC4800
- check output for boolean ARRAY
- check output for VISIBLE_STRING ARRAY
- check output for VISIBLE_STRING RECORD subitem
- dynamic/multiple PDOs
- more unit tests
- SM2 offset: regardles of value set, SDK generates RXPDO mappings as SDO1600. SM2 offset change affects
- `ecat_options.h` #define SM2_sma, MAX_TXPDO_SIZE and MAX_RXPDO_SIZE
- `esi.xml` `<Sm ControlByte="#x24" Enable="1" StartAddress="#x1600">Outputs</Sm>`
currently this tool mirrors SDK behavior, check if this hack is needed or just duplicated SDK bug
- calculate `MAX_RXPDO_SIZE`, `MAX_TXPDO_SIZE` based on selected SM2 offset
- modular device profile
- set project name from device name
- DC config in ESI .bin
- use [XML parsing](https://www.w3schools.com/xml/xml_parser.asp) for cleaner builiding ESI XML output
- read XML files: make reverse eningeering 3rd party devices easier
- add indexes list to OD model, to decrease parametes count in methods
- 0x1001 UINT8 Error Register is mandatory according to CiA DS301
- 0x1C12/0x1C13 `PdoAssign` and 0x1608/0x1A08 `PdoMapping` [should be RW](https://infosys.beckhoff.com/english.php?content=../content/1033/el6695/1317558667.html&id=)
### Code optimization
- reuse repeated string constants in objlist.c names, test if does any good or compiler does that for you anyway
# Disclaimer
The EtherCAT Technology, the trade name and logo "EtherCAT" are the intellectual
property of, and protected by Beckhoff Automation GmbH.

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,415 @@
<head>
<meta charset="UTF-8">
<title>🔁 EEPROM Generator | ESI, EEPORM, SOES C code configuration tool</title>
<!--
* SOES EEPROM generator
* This tool serves as:
- EtherCAT Slave Information XML + EEPROM binary generator
- SOES code generator
* Victor Sluiter 2013-2018
* Kuba Buda 2020-2021
-->
<script src="lib/jszip.min.js"></script>
<script src="src/constants.js"></script>
<script src="src/od.js"></script>
<script src="src/file_io.js"></script>
<script src="src/backup.js"></script>
<script src="src/generators/ecat_options.js"></script>
<script src="src/generators/EEPROM.js"></script>
<script src="src/generators/esi_xml.js"></script>
<script src="src/generators/objectlist.js"></script>
<script src="src/generators/utypes.js"></script>
<script src="src/ui.js"></script>
<link rel="stylesheet" href="styles/styles.css">
</head>
<body>
<!-- Modal dialog to edit OD objects -->
<div id="editObjectModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close" onClick="odModalClose()">&times;</span>
<h3 id="editObjectTitle">Edit object</h3>
<form name="EditObjectForm" id="EditObjectForm" action="" onsubmit="onEditObjectSubmit(this); return false;">
<table border="0">
<tr id="dialogRowIndex">
<td>Index:</td>
<td><input type = "text" name="Index" value="0x6000" title="Enter OD index for the new object" id="modalInputIndex"></td>
</tr>
<tr>
<td>Name of Object:</td>
<td><input type = "text" name="ObjectName" value="" title="Enter name for the new object" id="modalInputObjectName"></td>
</tr>
<tr id="dialogRowDtype">
<td>Data Type</td>
<td><select type = "text" name="DTYPE" title="Enter data type of new object">
<option value="BOOLEAN">BOOLEAN</option>
<option value="INTEGER8">INTEGER8</option>
<option value="INTEGER16">INTEGER16</option>
<option value="INTEGER32">INTEGER32</option>
<option value="UNSIGNED8">UNSIGNED8</option>
<option value="UNSIGNED16">UNSIGNED16</option>
<option value="UNSIGNED32">UNSIGNED32</option>
<option value="REAL32">REAL32</option>
<option value="VISIBLE_STRING">VISIBLE STRING</option>
</select></td>
</tr>
<tr id="dialogRowValue">
<td>Value:</td>
<td><input type = "text" name="InitalValue" value="0" title="Enter initial value for the new object"></td>
</tr>
<tr id="dialogRowAccess">
<td>Access:</td>
<td><select type = "text" name="Access" title="Enter access mode of new object">
<option value="RO">RO</option>
<option value="RW">RW</option>
<option value="WO">WO</option>
<option value="RWpre">RWpre</option>
</select></td>
</tr>
<tr>
<td align="center">
<button name="Cancel" onClick='odModalClose();'>✖️ &nbsp; Cancel &nbsp;</button>
</td><td>
<input type="submit" name="SaveChanges" value="✔️&nbsp; Save changes &nbsp;">
</td>
</tr>
</table>
</form>
</div>
</div>
<!-- Modal dialog to edit synchronization modes -->
<div id="syncModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close" onClick="syncModalClose()">&times;</span>
<h3 id="editObjectTitle">Edit synchronization mode</h3>
<form name="syncModalForm" id="syncModalForm" action="" onsubmit="onSyncSubmit(this); return false;">
<table border="0">
<tr>
<td>Name:</td>
<td><input type = "text" name="Name" value="New sync modee" title="Enter Name for new sync mode" id="syncModalName"></td>
</tr>
<tr>
<td>Description:</td>
<td><input type = "text" name="Description" value="New sync mode description" title="Enter Description for new sync mode" id="syncModalDescription"></td>
</tr>
<tr>
<td>Assign Activate:</td>
<td><input type = "text" name="AssignActivate" value="0x0" title="Enter Assign Activate value for new sync mode" id="syncModalAssignActivate"></td>
</tr>
</table>
<table border="0">
<tr>
<td>S0 cycle</td>
<td><input type = "text" name="Sync0cycleTime" value="0" title="Enter Sync0 cycle time" id="syncModalSync0cycleTime"></td>
<td>S0 shift</td>
<td><input type = "text" name="Sync0shiftTime" value="0" title="Enter Sync0 shift time" id="syncModalSync0shiftTime"></td>
</tr>
<tr>
<td>S1 cycle</td>
<td><input type = "text" name="Sync1cycleTime" value="0" title="Enter Sync1 cycle time" id="syncModalSync1cycleTime"></td>
<td>S1 shift</td>
<td><input type = "text" name="Sync1shiftTime" value="0" title="Enter Sync1 shift time" id="syncModalSync1shiftTime"></td>
</tr>
</table>
<table border="0" style="padding-top: 20px;">
<tr>
<td align="center">
<button name="Cancel" onClick='syncModalClose();'>✖️ &nbsp; Cancel &nbsp;</button>
</td><td>
<input type="submit" name="SaveChanges" value="✔️&nbsp; Save changes &nbsp;">
</td>
</tr>
</table>
</form>
</div>
</div>
<div class="settings-menu">
<span></span>
<h1> 🔁 EEPROM generator</h1>
<div>
<div class="dropdown">
<button class="dropbtn">⚙️ Settings</button>
<div class="dropdown-content">
<a href="#">
<button name="DarkMode" onClick='toggleDarkMode();' title="Toggle dark mode">
<span class="display-dark">🌞 Light mode</span>
<span class="display-light">🌜 Dark mode</span>
</button>
</a>
<a href="./tests.html">
<button name="tests" title="Run unit tests">
🔴🟢 Tests &nbsp;
</button>
</a>
</div>
</div>
</div>
</div>
<div class="main-wrapper">
<div id="pageHeading">
<h2> EtherCAT EEPROM + SOES OD configuration tool</h2>
<p>
This page serves as a tool to generate consistent data across your code, ESI file and EEPROM data.
Hover over the fields to get additional info about the items you can change. <br>
For more detail on how these values translate to the corresponding files, and where you can find more info in the ETG documentation,
look into javascript source that generates these files</a>.<br>
</p><p>
C source code is generated for SOES: Simple Open source EtherCAT Slave stack.
Its source code with example applications <a href="https://github.com/OpenEtherCATsociety/SOES">is available on Github</a>.
Docs with basic tutorial <a href="https://rt-labs.com/docs/soes/tutorial.html">can be found on rt-labs page</a>
</p>
</div>
<br>
<form name="SlaveForm" id="SlaveForm" action="" onsubmit="return false;">
<table border="0">
<tr>
<td><strong>Item:</strong></td><td><strong>Value</strong></td><td width="12%"><strong>ESI</strong></td><td width="12%"><strong>EEPROM</strong></td><td width="12%"><strong>objectlist.c</strong></td><td width="12%"><strong>ecat_options.h</strong></td>
</tr>
<tr>
<td>Name of Vendor:</td><td><input type = "text" name="VendorName" value="ACME EtherCAT Devices" title="Enter the name as how you are known in the EtherCAT Technology Group members list"></td><td>x</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>VendorID:</td><td><input type = "text" name="VendorID" value="0x000" title="You need to be member of the ETG to get a VendorID!&#013; Enter in hex (0x...) or decimal."></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr>
<td>ProductCode:</td><td><input type = "text" name="ProductCode" value="0x00ab123" title="The code of your Product...&#013;Each different product should have its own code.&#013;Enter in hex (0x...) or decimal."></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr>
<td>Profile No:</td><td><input type = "text" name="ProfileNo" value="5001" title="Profile number of your Product...&#013;Used to identify device type.&#013;Enter in hex (0x...) or decimal."></td><td>x</td><td></td><td></td><td>&nbsp;</td>
</tr>
<tr>
<td>RevisionNumber:</td><td><input type = "text" name="RevisionNumber" value="0x002" title="Revision of Product&#013; Enter in hex (0x...) or decimal."></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr>
<td>SerialNumber:</td><td><input type = "text" name="SerialNumber" value="0x001" title="Serial number; may be used in production.&#013; Enter in hex (0x...) or decimal."></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr>
<td>Hardware version:</td><td><input type = "text" name="HWversion" value="0.0.1" title="Enter HW version as string"><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr>
<td>Software version:</td><td><input type = "text" name="SWversion" value="0.0.1" title="Enter SW version as string"></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr>
<td>Number of <strong>bytes</strong> in EEPROM:</td><td><input type = "text" name="EEPROMsize" value="2048" title="Number of bytes in EEPROM; 2048&#013;2048*8=16kilobit&#013;Enter in hex (0x...) or decimal."></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>RxMailbox Offset:</td><td><input type = "text" name="RxMailboxOffset" value="0x1000" title="Offset in memory. Do not change unless you are really sure what you're doing"></td><td>x</td><td>x</td><td>&nbsp;</td><td>x</td>
</tr>
<tr>
<td>TxMailbox Offset:</td><td><input type = "text" name="TxMailboxOffset" value="0x1200" title="Offset in memory. Do not change unless you are really sure what you're doing"></td><td>x</td><td>x</td><td>&nbsp;</td><td>x</td>
</tr>
<tr>
<td>Tx AND Rx Mailbox Size:</td><td><input type = "text" name="MailboxSize" value="512" title="Size of mailbox in bytes.&#013;Do not change unless you are really sure what you're doing."></td><td>x</td><td>x</td><td>&nbsp;</td><td>x</td>
</tr>
<tr>
<td>SyncManager 2 offset:</td><td><input type = "text" name="SM2Offset" value="0x1600" title="Offset in memory. Do not change unless you are really sure what you're doing"></td><td>x</td><td>x</td><td>&nbsp;</td><td>x</td>
</tr>
<tr>
<td>SyncManager 3 offset:</td><td><input type = "text" name="SM3Offset" value="0x1A00" title="Offset in memory. Do not change unless you are really sure what you're doing"></td><td>x</td><td>x</td><td>&nbsp;</td><td>x</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr>
<td>Group Type:</td><td><input type = "text" name="TextGroupType" value="DigIn" title="Group Type, like DigIn, DigOut, Measurement, ...&#013;See ETG2000, this is used for grouping in the ESI file"></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Group Name:</td><td><input type = "text" name="TextGroupName5" value="Digital input" title="Description of Group&#013;See ETG2000, this is used for grouping in the ESI file"></td><td>x</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Image Name:</td><td><input type = "text" name="ImageName" value="IMGCBY" title="Name of image used in EtherCAT Configurator"></td><td>&nbsp;</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Device Type:</td><td><input type = "text" name="TextDeviceType" value="DigIn2000" title="Name or order code of product"></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Device Name:</td><td><input type = "text" name="TextDeviceName" value="2-channel Hypergalactic input superimpermanator" title="Description of your device"></td><td>x</td><td>x</td><td>x</td><td>&nbsp;</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr>
<td>Port 0 Physical interface:</td><td><select name="Port0Physical" title="Select which physical connection is used on each physical port of your Beckhoff ASIC (ET1100, ET1200)">
<option value="Y" selected>MII</option>
<option value="H">MII-Fast Hot Connect</option>
<option value="K">EBUS</option>
<option value=" ">Not Used</option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Port 1 Physical interface:</td><td><select name="Port1Physical" title="Select which physical connection is used on each physical port of your Beckhoff ASIC (ET1100, ET1200)">
<option value="Y" selected>MII</option>
<option value="H">MII-Fast Hot Connect</option>
<option value="K">EBUS</option>
<option value=" ">Not Used</option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Port 2 Physical interface:</td><td><select name="Port2Physical" title="Select which physical connection is used on each physical port of your Beckhoff ASIC (ET1100, ET1200)">
<option value="Y">MII</option>
<option value="H">MII-Fast Hot Connect</option>
<option value="K">EBUS</option>
<option value=" " selected>Not Used</option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>Port 3 Physical interface:</td><td><select name="Port3Physical" title="Select which physical connection is used on each physical port of your Beckhoff ASIC (ET1100, ET1200)">
<option value="Y">MII</option>
<option value="H">MII-Fast Hot Connect</option>
<option value="K">EBUS</option>
<option value=" " selected>Not Used</option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr>
<td>ESC (EtherCAT Slave Chip):</td><td><select name="ESC" title="Select which ESC (EtherCAT Slave Chip) is used">
<option value="AX58100">AX58100</option>
<option value="ET1100">ET1100</option>
<option value="LAN9252">LAN9252</option>
<option value="LAN9253 Beckhoff">LAN9253 Beckhoff</option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr>
<td>SPI mode:</td><td><select name="SPImode" title="Select which SPI mode is used">
<option value="3">3 (CPOL=1 CPHA=1) </option>
<option value="2">2 (CPOL=1 CPHA=0) </option>
<option value="1">1 (CPOL=0 CPHA=1) </option>
<option value="0">0 (CPOL=0 CPHA=0) </option>
</select></td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr><td>
<input type="checkbox" name="CoeDetailsEnableSDO" value="EnableSDO" checked>Enable SDO<br>
<input type="checkbox" name="CoeDetailsEnableSDOInfo" value="EnableSDOInfo" checked>Enable SDO Info<br>
<input type="checkbox" name="CoeDetailsEnablePDOAssign" value="EnablePDOAssign">Enable PDO Assign<br>
<input type="checkbox" name="CoeDetailsEnablePDOConfiguration" value="EnablePDOConfiguration">Enable PDO Configuration<br>
<input type="checkbox" name="CoeDetailsEnableUploadAtStartup" value="EnableUploadAtStartup" checked>Enable Upload at startup<br>
<input type="checkbox" name="CoeDetailsEnableSDOCompleteAccess" value="EnableSDOCompleteAccess">Enable SDO complete access <br>
</td><td>&nbsp;</td><td>x</td><td>x</td><td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr><td colspan="6"><hr></td</tr>
<tr><td colspan="6">
<h3 class="odSectionHeading">
<span class="odSectionTitle"> SDO </span>
<span>
<button onClick="editVAR_Click(sdo)"> Add variable</button>
<button onClick="editARRAY_Click(sdo)"> Add Array</button>
<button onClick="editRECORD_Click(sdo)"> Add Record</button>
</span>
</h3>
<div id="tr_sdo"></div>
</td></tr>
<tr><td colspan="6"><hr></td</tr>
<tr><td colspan="6">
<h3 class="odSectionHeading">
<span title="We are assuming single, fixed TxPDO" class="odSectionTitle"> TXPDO (slv to master) </span>
<span>
<button onClick="editVAR_Click(txpdo)"> Add variable</button>
<button onClick="editARRAY_Click(txpdo)"> Add Array</button>
<button onClick="editRECORD_Click(txpdo)"> Add Record</button>
</span>
</h3>
<div id="tr_txpdo"></div>
</td></tr>
<tr><td colspan="6"><hr></td</tr>
<tr><td colspan="6">
<h3 class="odSectionHeading">
<span title="We are assuming single, fixed RxPDO" class="odSectionTitle"> RXPDO (master to slv) </span>
<span>
<button onClick="editVAR_Click(rxpdo)"> Add variable</button>
<button onClick="editARRAY_Click(rxpdo)"> Add Array</button>
<button onClick="editRECORD_Click(rxpdo)"> Add Record</button>
</span>
</h3>
<div id="tr_rxpdo"></div>
</td></tr>
<tr><td colspan="6"><hr></td</tr>
<h3 class="odSectionHeading">
<span title="Distributed Clocks configuration" class="odSectionTitle"> Synchronization </span>
<span>
<button onClick="addSyncClick()"> Add DC settings</button>
</span>
</h3>
<div id="dcSyncModes"></div>
<tr><td colspan="6"><hr></td</tr>
<tr><td colspan="6" align="center">
<button name="GenerateDownload" onclick="onGenerateDownloadClick()" title="Generate all files and download at once">
&nbsp; Download all files &nbsp;
</button>
<button id='GenerateFilesButton' name="Generate" onClick="onGenerateClick();" title="Generate all files">
&nbsp; Generate files &nbsp;
</button>
<button name="Save" onClick='onSaveClick();' title="Download current settings and Object Dictionary state as JSON file">
⤵️ &nbsp; Save project &nbsp;
</button>
<button name="Restore" onClick='onRestoreClick();' title="Load settings and Object Dictionary state from previously saved JSON file">
⤴️ &nbsp; Restore project &nbsp;
</button>
<button name="Reset" onClick='onResetClick();' title="Reset settings to default values and remove all Object Dictionary entries">
‼️ &nbsp; Reset project &nbsp;
</button>
</td></tr>
</table>
</form>
<br><br>
<form name="outCodeForm" id="outCodeForm" spellcheck="false" action="" onsubmit="return false;">
ESI file:
<button name="DownloadESI" onClick="onDownloadEsiXmlClick()">
🔽 &nbsp; Download &nbsp;
</button>
<br>
<textarea cols = "150" rows = "12" name="ESI">ESI File</textarea><br><br>
EEPROM HEX file:
<button name="Download_HEX"
onClick="downloadFile(getOutputForm().HEX.value, fileName = 'eeprom.hex', contentType = 'application/octet-stream');">
🔽 &nbsp; Download &nbsp;
</button>
<button name="Download_BIN" onClick="onDownloadBinClick();">
🔽 &nbsp; Download .bin &nbsp;
</button>
<br>
To flash slave device with generated EEPROM content, you can <a href="https://github.com/OpenEtherCATsociety/SOEM/">install SOEM</a>
and run command (for Intel Hex):
<br><br><i title="For binary, use flag -w instead of -wi" id="hexInstallCmd">sudo ./eepromtool 1 eth0 -wi eeprom.hex</i><br><br>
<textarea cols = "150" rows = "12" name="HEX">Hex File</textarea><br><br>
ecat_options.h:
<button name="Download_ecat_options"
onClick="downloadFile(getOutputForm().ecat_options.value, fileName = 'ecat_options.h', contentType = 'text/plain');">
🔽 &nbsp; Download &nbsp;
</button>
<br>
<textarea cols = "150" rows = "12" name="ecat_options">ecat_options.h</textarea><br><br>
objectlist.c:
<button name="Download_objectlist"
onClick="downloadFile(getOutputForm().objectlist.value, fileName = 'objectlist.c', contentType = 'text/plain');">
🔽 &nbsp; Download &nbsp;
</button>
<br>
<textarea cols = "150" rows = "12" name="objectlist">objectlist.c</textarea><br><br>
utypes.h:
<button name="Download_utypes"
onClick="downloadFile(getOutputForm().utypes.value, fileName = 'utypes.h', contentType = 'text/plain');">
🔽 &nbsp; Download &nbsp;
</button>
<br>
<textarea cols = "150" rows = "12" name="utypes">utypes.h</textarea><br><br>
<input id="restoreFileInput" type='file' accept=".json" style="visibility:hidden;" onchange="readFile(event)" />
</form>
</div>
</body>

View File

@@ -0,0 +1,139 @@
/**
Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
[jasmine-gem]: http://github.com/pivotal/jasmine-gem
*/
(function() {
var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
/**
* ## Require &amp; Instantiate
*
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/
var jasmine = jasmineRequire.core(jasmineRequire),
global = jasmine.getGlobal();
global.jasmine = jasmine;
/**
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
*/
jasmineRequire.html(jasmine);
/**
* Create the Jasmine environment. This is used to run all specs in a project.
*/
var env = jasmine.getEnv();
/**
* ## The Global Interface
*
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
*/
var jasmineInterface = jasmineRequire.interface(jasmine, env);
/**
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
*/
extend(global, jasmineInterface);
/**
* ## Runner Parameters
*
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
*/
var queryString = new jasmine.QueryString({
getWindowLocation: function() { return window.location; }
});
var filterSpecs = !!queryString.getParam("spec");
var config = {
failFast: queryString.getParam("failFast"),
oneFailurePerSpec: queryString.getParam("oneFailurePerSpec"),
hideDisabled: queryString.getParam("hideDisabled")
};
var random = queryString.getParam("random");
if (random !== undefined && random !== "") {
config.random = random;
}
var seed = queryString.getParam("seed");
if (seed) {
config.seed = seed;
}
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
var htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function(key, value) { return queryString.navigateWithNewParam(key, value); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: function() { return document.body; },
createElement: function() { return document.createElement.apply(document, arguments); },
createTextNode: function() { return document.createTextNode.apply(document, arguments); },
timer: new jasmine.Timer(),
filterSpecs: filterSpecs
});
/**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/
env.addReporter(jasmineInterface.jsApiReporter);
env.addReporter(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
var specFilter = new jasmine.HtmlSpecFilter({
filterString: function() { return queryString.getParam("spec"); }
});
config.specFilter = function(spec) {
return specFilter.matches(spec.getFullName());
};
env.configure(config);
/**
* Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
*/
window.setTimeout = window.setTimeout;
window.setInterval = window.setInterval;
window.clearTimeout = window.clearTimeout;
window.clearInterval = window.clearInterval;
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
htmlReporter.initialize();
env.execute();
};
/**
* Helper function for readability above.
*/
function extend(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
}
}());

View File

@@ -0,0 +1,853 @@
/*
Copyright (c) 2008-2021 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
jasmineRequire.html = function(j$) {
j$.ResultsNode = jasmineRequire.ResultsNode();
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.QueryString = jasmineRequire.QueryString();
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
};
jasmineRequire.HtmlReporter = function(j$) {
function ResultsStateBuilder() {
this.topResults = new j$.ResultsNode({}, '', null);
this.currentParent = this.topResults;
this.specsExecuted = 0;
this.failureCount = 0;
this.pendingSpecCount = 0;
}
ResultsStateBuilder.prototype.suiteStarted = function(result) {
this.currentParent.addChild(result, 'suite');
this.currentParent = this.currentParent.last();
};
ResultsStateBuilder.prototype.suiteDone = function(result) {
this.currentParent.updateResult(result);
if (this.currentParent !== this.topResults) {
this.currentParent = this.currentParent.parent;
}
if (result.status === 'failed') {
this.failureCount++;
}
};
ResultsStateBuilder.prototype.specStarted = function(result) {};
ResultsStateBuilder.prototype.specDone = function(result) {
this.currentParent.addChild(result, 'spec');
if (result.status !== 'excluded') {
this.specsExecuted++;
}
if (result.status === 'failed') {
this.failureCount++;
}
if (result.status == 'pending') {
this.pendingSpecCount++;
}
};
function HtmlReporter(options) {
var config = function() {
return (options.env && options.env.configuration()) || {};
},
getContainer = options.getContainer,
createElement = options.createElement,
createTextNode = options.createTextNode,
navigateWithNewParam = options.navigateWithNewParam || function() {},
addToExistingQueryString =
options.addToExistingQueryString || defaultQueryString,
filterSpecs = options.filterSpecs,
htmlReporterMain,
symbols,
deprecationWarnings = [];
this.initialize = function() {
clearPrior();
htmlReporterMain = createDom(
'div',
{ className: 'jasmine_html-reporter' },
createDom(
'div',
{ className: 'jasmine-banner' },
createDom('a', {
className: 'jasmine-title',
href: 'http://jasmine.github.io/',
target: '_blank'
}),
createDom('span', { className: 'jasmine-version' }, j$.version)
),
createDom('ul', { className: 'jasmine-symbol-summary' }),
createDom('div', { className: 'jasmine-alert' }),
createDom(
'div',
{ className: 'jasmine-results' },
createDom('div', { className: 'jasmine-failures' })
)
);
getContainer().appendChild(htmlReporterMain);
};
var totalSpecsDefined;
this.jasmineStarted = function(options) {
totalSpecsDefined = options.totalSpecsDefined || 0;
};
var summary = createDom('div', { className: 'jasmine-summary' });
var stateBuilder = new ResultsStateBuilder();
this.suiteStarted = function(result) {
stateBuilder.suiteStarted(result);
};
this.suiteDone = function(result) {
stateBuilder.suiteDone(result);
if (result.status === 'failed') {
failures.push(failureDom(result));
}
addDeprecationWarnings(result, 'suite');
};
this.specStarted = function(result) {
stateBuilder.specStarted(result);
};
var failures = [];
this.specDone = function(result) {
stateBuilder.specDone(result);
if (noExpectations(result)) {
var noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
if (result.status === 'failed') {
console.error(noSpecMsg);
} else {
console.warn(noSpecMsg);
}
}
if (!symbols) {
symbols = find('.jasmine-symbol-summary');
}
symbols.appendChild(
createDom('li', {
className: this.displaySpecInCorrectFormat(result),
id: 'spec_' + result.id,
title: result.fullName
})
);
if (result.status === 'failed') {
failures.push(failureDom(result));
}
addDeprecationWarnings(result, 'spec');
};
this.displaySpecInCorrectFormat = function(result) {
return noExpectations(result) && result.status === 'passed'
? 'jasmine-empty'
: this.resultStatus(result.status);
};
this.resultStatus = function(status) {
if (status === 'excluded') {
return config().hideDisabled
? 'jasmine-excluded-no-display'
: 'jasmine-excluded';
}
return 'jasmine-' + status;
};
this.jasmineDone = function(doneResult) {
var banner = find('.jasmine-banner');
var alert = find('.jasmine-alert');
var order = doneResult && doneResult.order;
var i;
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-duration' },
'finished in ' + doneResult.totalTime / 1000 + 's'
)
);
banner.appendChild(optionsMenu(config()));
if (stateBuilder.specsExecuted < totalSpecsDefined) {
var skippedMessage =
'Ran ' +
stateBuilder.specsExecuted +
' of ' +
totalSpecsDefined +
' specs - run all';
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
var skippedLink =
(window.location.pathname || '') +
addToExistingQueryString('spec', '');
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-skipped' },
createDom(
'a',
{ href: skippedLink, title: 'Run all specs' },
skippedMessage
)
)
);
}
var statusBarMessage = '';
var statusBarClassName = 'jasmine-overall-result jasmine-bar ';
var globalFailures = (doneResult && doneResult.failedExpectations) || [];
var failed = stateBuilder.failureCount + globalFailures.length > 0;
if (totalSpecsDefined > 0 || failed) {
statusBarMessage +=
pluralize('spec', stateBuilder.specsExecuted) +
', ' +
pluralize('failure', stateBuilder.failureCount);
if (stateBuilder.pendingSpecCount) {
statusBarMessage +=
', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
}
}
if (doneResult.overallStatus === 'passed') {
statusBarClassName += ' jasmine-passed ';
} else if (doneResult.overallStatus === 'incomplete') {
statusBarClassName += ' jasmine-incomplete ';
statusBarMessage =
'Incomplete: ' +
doneResult.incompleteReason +
', ' +
statusBarMessage;
} else {
statusBarClassName += ' jasmine-failed ';
}
var seedBar;
if (order && order.random) {
seedBar = createDom(
'span',
{ className: 'jasmine-seed-bar' },
', randomized with seed ',
createDom(
'a',
{
title: 'randomized with seed ' + order.seed,
href: seedHref(order.seed)
},
order.seed
)
);
}
alert.appendChild(
createDom(
'span',
{ className: statusBarClassName },
statusBarMessage,
seedBar
)
);
var errorBarClassName = 'jasmine-bar jasmine-errored';
var afterAllMessagePrefix = 'AfterAll ';
for (i = 0; i < globalFailures.length; i++) {
alert.appendChild(
createDom(
'span',
{ className: errorBarClassName },
globalFailureMessage(globalFailures[i])
)
);
}
function globalFailureMessage(failure) {
if (failure.globalErrorType === 'load') {
var prefix = 'Error during loading: ' + failure.message;
if (failure.filename) {
return (
prefix + ' in ' + failure.filename + ' line ' + failure.lineno
);
} else {
return prefix;
}
} else {
return afterAllMessagePrefix + failure.message;
}
}
addDeprecationWarnings(doneResult);
for (i = 0; i < deprecationWarnings.length; i++) {
var context;
switch (deprecationWarnings[i].runnableType) {
case 'spec':
context = '(in spec: ' + deprecationWarnings[i].runnableName + ')';
break;
case 'suite':
context = '(in suite: ' + deprecationWarnings[i].runnableName + ')';
break;
default:
context = '';
}
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-warning' },
'DEPRECATION: ' + deprecationWarnings[i].message,
createDom('br'),
context
)
);
}
var results = find('.jasmine-results');
results.appendChild(summary);
summaryList(stateBuilder.topResults, summary);
if (failures.length) {
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
createDom('span', {}, 'Spec List | '),
createDom(
'a',
{ className: 'jasmine-failures-menu', href: '#' },
'Failures'
)
)
);
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
createDom(
'a',
{ className: 'jasmine-spec-list-menu', href: '#' },
'Spec List'
),
createDom('span', {}, ' | Failures ')
)
);
find('.jasmine-failures-menu').onclick = function() {
setMenuModeTo('jasmine-failure-list');
return false;
};
find('.jasmine-spec-list-menu').onclick = function() {
setMenuModeTo('jasmine-spec-list');
return false;
};
setMenuModeTo('jasmine-failure-list');
var failureNode = find('.jasmine-failures');
for (i = 0; i < failures.length; i++) {
failureNode.appendChild(failures[i]);
}
}
};
return this;
function failureDom(result) {
var failure = createDom(
'div',
{ className: 'jasmine-spec-detail jasmine-failed' },
failureDescription(result, stateBuilder.currentParent),
createDom('div', { className: 'jasmine-messages' })
);
var messages = failure.childNodes[1];
for (var i = 0; i < result.failedExpectations.length; i++) {
var expectation = result.failedExpectations[i];
messages.appendChild(
createDom(
'div',
{ className: 'jasmine-result-message' },
expectation.message
)
);
messages.appendChild(
createDom(
'div',
{ className: 'jasmine-stack-trace' },
expectation.stack
)
);
}
if (result.failedExpectations.length === 0) {
messages.appendChild(
createDom(
'div',
{ className: 'jasmine-result-message' },
'Spec has no expectations'
)
);
}
return failure;
}
function summaryList(resultsTree, domParent) {
var specListNode;
for (var i = 0; i < resultsTree.children.length; i++) {
var resultNode = resultsTree.children[i];
if (filterSpecs && !hasActiveSpec(resultNode)) {
continue;
}
if (resultNode.type === 'suite') {
var suiteListNode = createDom(
'ul',
{ className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
createDom(
'li',
{
className:
'jasmine-suite-detail jasmine-' + resultNode.result.status
},
createDom(
'a',
{ href: specHref(resultNode.result) },
resultNode.result.description
)
)
);
summaryList(resultNode, suiteListNode);
domParent.appendChild(suiteListNode);
}
if (resultNode.type === 'spec') {
if (domParent.getAttribute('class') !== 'jasmine-specs') {
specListNode = createDom('ul', { className: 'jasmine-specs' });
domParent.appendChild(specListNode);
}
var specDescription = resultNode.result.description;
if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
if (
resultNode.result.status === 'pending' &&
resultNode.result.pendingReason !== ''
) {
specDescription =
specDescription +
' PENDING WITH MESSAGE: ' +
resultNode.result.pendingReason;
}
specListNode.appendChild(
createDom(
'li',
{
className: 'jasmine-' + resultNode.result.status,
id: 'spec-' + resultNode.result.id
},
createDom(
'a',
{ href: specHref(resultNode.result) },
specDescription
)
)
);
}
}
}
function optionsMenu(config) {
var optionsMenuDom = createDom(
'div',
{ className: 'jasmine-run-options' },
createDom('span', { className: 'jasmine-trigger' }, 'Options'),
createDom(
'div',
{ className: 'jasmine-payload' },
createDom(
'div',
{ className: 'jasmine-stop-on-failure' },
createDom('input', {
className: 'jasmine-fail-fast',
id: 'jasmine-fail-fast',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-fail-fast' },
'stop execution on spec failure'
)
),
createDom(
'div',
{ className: 'jasmine-throw-failures' },
createDom('input', {
className: 'jasmine-throw',
id: 'jasmine-throw-failures',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-throw-failures' },
'stop spec on expectation failure'
)
),
createDom(
'div',
{ className: 'jasmine-random-order' },
createDom('input', {
className: 'jasmine-random',
id: 'jasmine-random-order',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-random-order' },
'run tests in random order'
)
),
createDom(
'div',
{ className: 'jasmine-hide-disabled' },
createDom('input', {
className: 'jasmine-disabled',
id: 'jasmine-hide-disabled',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-hide-disabled' },
'hide disabled tests'
)
)
)
);
var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast');
failFastCheckbox.checked = config.failFast;
failFastCheckbox.onclick = function() {
navigateWithNewParam('failFast', !config.failFast);
};
var throwCheckbox = optionsMenuDom.querySelector(
'#jasmine-throw-failures'
);
throwCheckbox.checked = config.oneFailurePerSpec;
throwCheckbox.onclick = function() {
navigateWithNewParam('oneFailurePerSpec', !config.oneFailurePerSpec);
};
var randomCheckbox = optionsMenuDom.querySelector(
'#jasmine-random-order'
);
randomCheckbox.checked = config.random;
randomCheckbox.onclick = function() {
navigateWithNewParam('random', !config.random);
};
var hideDisabled = optionsMenuDom.querySelector('#jasmine-hide-disabled');
hideDisabled.checked = config.hideDisabled;
hideDisabled.onclick = function() {
navigateWithNewParam('hideDisabled', !config.hideDisabled);
};
var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
isOpen = /\bjasmine-open\b/;
optionsTrigger.onclick = function() {
if (isOpen.test(optionsPayload.className)) {
optionsPayload.className = optionsPayload.className.replace(
isOpen,
''
);
} else {
optionsPayload.className += ' jasmine-open';
}
};
return optionsMenuDom;
}
function failureDescription(result, suite) {
var wrapper = createDom(
'div',
{ className: 'jasmine-description' },
createDom(
'a',
{ title: result.description, href: specHref(result) },
result.description
)
);
var suiteLink;
while (suite && suite.parent) {
wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild);
suiteLink = createDom(
'a',
{ href: suiteHref(suite) },
suite.result.description
);
wrapper.insertBefore(suiteLink, wrapper.firstChild);
suite = suite.parent;
}
return wrapper;
}
function suiteHref(suite) {
var els = [];
while (suite && suite.parent) {
els.unshift(suite.result.description);
suite = suite.parent;
}
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
return (
(window.location.pathname || '') +
addToExistingQueryString('spec', els.join(' '))
);
}
function addDeprecationWarnings(result, runnableType) {
if (result && result.deprecationWarnings) {
for (var i = 0; i < result.deprecationWarnings.length; i++) {
var warning = result.deprecationWarnings[i].message;
if (!j$.util.arrayContains(warning)) {
deprecationWarnings.push({
message: warning,
runnableName: result.fullName,
runnableType: runnableType
});
}
}
}
}
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
function clearPrior() {
// return the reporter
var oldReporter = find('');
if (oldReporter) {
getContainer().removeChild(oldReporter);
}
}
function createDom(type, attrs, childrenVarArgs) {
var el = createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == 'className') {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
}
function pluralize(singular, count) {
var word = count == 1 ? singular : singular + 's';
return '' + count + ' ' + word;
}
function specHref(result) {
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
return (
(window.location.pathname || '') +
addToExistingQueryString('spec', result.fullName)
);
}
function seedHref(seed) {
// include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
return (
(window.location.pathname || '') +
addToExistingQueryString('seed', seed)
);
}
function defaultQueryString(key, value) {
return '?' + key + '=' + value;
}
function setMenuModeTo(mode) {
htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
}
function noExpectations(result) {
var allExpectations =
result.failedExpectations.length + result.passedExpectations.length;
return (
allExpectations === 0 &&
(result.status === 'passed' || result.status === 'failed')
);
}
function hasActiveSpec(resultNode) {
if (resultNode.type == 'spec' && resultNode.result.status != 'excluded') {
return true;
}
if (resultNode.type == 'suite') {
for (var i = 0, j = resultNode.children.length; i < j; i++) {
if (hasActiveSpec(resultNode.children[i])) {
return true;
}
}
}
}
}
return HtmlReporter;
};
jasmineRequire.HtmlSpecFilter = function() {
function HtmlSpecFilter(options) {
var filterString =
options &&
options.filterString() &&
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
var filterPattern = new RegExp(filterString);
this.matches = function(specName) {
return filterPattern.test(specName);
};
}
return HtmlSpecFilter;
};
jasmineRequire.ResultsNode = function() {
function ResultsNode(result, type, parent) {
this.result = result;
this.type = type;
this.parent = parent;
this.children = [];
this.addChild = function(result, type) {
this.children.push(new ResultsNode(result, type, this));
};
this.last = function() {
return this.children[this.children.length - 1];
};
this.updateResult = function(result) {
this.result = result;
};
}
return ResultsNode;
};
jasmineRequire.QueryString = function() {
function QueryString(options) {
this.navigateWithNewParam = function(key, value) {
options.getWindowLocation().search = this.fullStringWithNewParam(
key,
value
);
};
this.fullStringWithNewParam = function(key, value) {
var paramMap = queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
};
this.getParam = function(key) {
return queryStringToParamMap()[key];
};
return this;
function toQueryString(paramMap) {
var qStrPairs = [];
for (var prop in paramMap) {
qStrPairs.push(
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
);
}
return '?' + qStrPairs.join('&');
}
function queryStringToParamMap() {
var paramStr = options.getWindowLocation().search.substring(1),
params = [],
paramMap = {};
if (paramStr.length > 0) {
params = paramStr.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var value = decodeURIComponent(p[1]);
if (value === 'true' || value === 'false') {
value = JSON.parse(value);
}
paramMap[decodeURIComponent(p[0])] = value;
}
}
return paramMap;
}
}
return QueryString;
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="UTF-8"?>
<EtherCATInfo>
<Vendor>
<Id>0</Id>
<Name LcId="1033">ACME EtherCAT Devices</Name>
</Vendor>
<Descriptions>
<Groups>
<Group>
<Type>DigIn</Type>
<Name LcId="1033">Digital input</Name>
</Group>
</Groups>
<Devices>
<Device Physics="YY ">
<Type ProductCode="#xab123" RevisionNo="#x2">DigIn2000</Type>
<Name LcId="1033">2-channel Hypergalactic input superimpermanator</Name>
<GroupType>DigIn</GroupType>
<Profile>
<ProfileNo>5001</ProfileNo>
<AddInfo>0</AddInfo>
<Dictionary>
<DataTypes>
<DataType>
<Name>DT1018</Name>
<BitSize>144</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>1</SubIdx>
<Name>Vendor ID</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>2</SubIdx>
<Name>Product Code</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>48</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>3</SubIdx>
<Name>Revision Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>80</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>4</SubIdx>
<Name>Serial Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>112</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>DT1C00ARR</Name>
<BaseType>USINT</BaseType>
<BitSize>32</BitSize>
<ArrayInfo>
<LBound>1</LBound>
<Elements>4</Elements>
</ArrayInfo>
</DataType>
<DataType>
<Name>DT1C00</Name>
<BitSize>48</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<Name>Elements</Name>
<Type>DT1C00ARR</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>UDINT</Name>
<BitSize>32</BitSize>
</DataType>
<DataType>
<Name>STRING(47)</Name>
<BitSize>376</BitSize>
</DataType>
<DataType>
<Name>STRING(5)</Name>
<BitSize>40</BitSize>
</DataType>
<DataType>
<Name>USINT</Name>
<BitSize>8</BitSize>
</DataType>
</DataTypes>
<Objects>
<Object>
<Index>#x1000</Index>
<Name>Device Type</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<Info>
<DefaultValue>5001</DefaultValue>
</Info>
<Flags>
<Access>ro</Access>
<Category>m</Category>
</Flags>
</Object>
<Object>
<Index>#x1008</Index>
<Name>Device Name</Name>
<Type>STRING(47)</Type>
<BitSize>376</BitSize>
<Info>
<DefaultString>2-channel Hypergalactic input superimpermanator</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1009</Index>
<Name>Hardware Version</Name>
<Type>STRING(5)</Type>
<BitSize>40</BitSize>
<Info>
<DefaultString>0.0.1</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
<Category>o</Category>
</Flags>
</Object>
<Object>
<Index>#x100A</Index>
<Name>Software Version</Name>
<Type>STRING(5)</Type>
<BitSize>40</BitSize>
<Info>
<DefaultString>0.0.1</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1018</Index>
<Name>Identity Object</Name>
<Type>DT1018</Type>
<BitSize>144</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Vendor ID</Name>
<Info>
<DefaultValue>0</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Product Code</Name>
<Info>
<DefaultValue>700707</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Revision Number</Name>
<Info>
<DefaultValue>2</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Serial Number</Name>
<Info>
<DefaultValue>1</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1C00</Index>
<Name>Sync Manager Communication Type</Name>
<Type>DT1C00</Type>
<BitSize>48</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM0</Name>
<Info>
<DefaultValue>1</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM1</Name>
<Info>
<DefaultValue>2</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM2</Name>
<Info>
<DefaultValue>3</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM3</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
</Objects>
</Dictionary>
</Profile>
<Fmmu>Outputs</Fmmu>
<Fmmu>Inputs</Fmmu>
<Fmmu>MBoxState</Fmmu>
<Sm DefaultSize="512" StartAddress="#x1000" ControlByte="#x26" Enable="1">MBoxOut</Sm>
<Sm DefaultSize="512" StartAddress="#x1200" ControlByte="#x22" Enable="1">MBoxIn</Sm>
<Sm StartAddress="#x1600" ControlByte="#x24" Enable="0">Outputs</Sm>
<Sm StartAddress="#x1A00" ControlByte="#x20" Enable="0">Inputs</Sm>
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" PdoAssign="true" PdoConfig="true" PdoUpload="true" CompleteAccess="true" />
</Mailbox>
<Dc>
</Dc>
<Eeprom>
<ByteSize>2048</ByteSize>
<ConfigData>05060344640000</ConfigData>
</Eeprom>
</Device>
</Devices>
</Descriptions>
</EtherCATInfo>

View File

@@ -0,0 +1,281 @@
<?xml version="1.0" encoding="UTF-8"?>
<EtherCATInfo>
<Vendor>
<Id>600</Id>
<Name LcId="1033">oss</Name>
</Vendor>
<Descriptions>
<Groups>
<Group>
<Type>groupType</Type>
<Name LcId="1033">groupName</Name>
</Group>
</Groups>
<Devices>
<Device Physics="YY">
<Type ProductCode="0" RevisionNo="0">codegentool</Type>
<Name LcId="1033">slave</Name>
<GroupType>groupType</GroupType>
<Profile>
<ProfileNo>5001</ProfileNo>
<AddInfo>0</AddInfo>
<Dictionary>
<DataTypes>
<DataType>
<Name>DT1018</Name>
<BitSize>144</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>1</SubIdx>
<Name>Vendor ID</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>2</SubIdx>
<Name>Product Code</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>48</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>3</SubIdx>
<Name>Revision Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>80</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>4</SubIdx>
<Name>Serial Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>112</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>DT1C00ARR</Name>
<BaseType>USINT</BaseType>
<BitSize>32</BitSize>
<ArrayInfo>
<LBound>1</LBound>
<Elements>4</Elements>
</ArrayInfo>
</DataType>
<DataType>
<Name>DT1C00</Name>
<BitSize>48</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<Name>Elements</Name>
<Type>DT1C00ARR</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>STRING(3)</Name>
<BitSize>24</BitSize>
</DataType>
<DataType>
<Name>STRING(11)</Name>
<BitSize>88</BitSize>
</DataType>
<DataType>
<Name>UDINT</Name>
<BitSize>32</BitSize>
</DataType>
<DataType>
<Name>USINT</Name>
<BitSize>8</BitSize>
</DataType>
</DataTypes>
<Objects>
<Object>
<Index>#x1000</Index>
<Name>Device Type</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<Info>
<DefaultValue>#x00001389</DefaultValue>
</Info>
<Flags>
<Access>ro</Access>
<Category>m</Category>
</Flags>
</Object>
<Object>
<Index>#x1008</Index>
<Name>Device Name</Name>
<Type>STRING(11)</Type>
<BitSize>88</BitSize>
<Info>
<DefaultString>codegentool</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1009</Index>
<Name>Hardware Version</Name>
<Type>STRING(3)</Type>
<BitSize>24</BitSize>
<Info>
<DefaultString>1.0</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
<Category>o</Category>
</Flags>
</Object>
<Object>
<Index>#x100A</Index>
<Name>Software Version</Name>
<Type>STRING(3)</Type>
<BitSize>24</BitSize>
<Info>
<DefaultString>1.0</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1018</Index>
<Name>Identity Object</Name>
<Type>DT1018</Type>
<BitSize>144</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Vendor ID</Name>
<Info>
<DefaultValue>600</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Product Code</Name>
<Info>
<DefaultValue>0</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Revision Number</Name>
<Info>
<DefaultValue>0</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Serial Number</Name>
<Info>
<DefaultValue>#x00000000</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1C00</Index>
<Name>Sync Manager Communication Type</Name>
<Type>DT1C00</Type>
<BitSize>48</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM0</Name>
<Info>
<DefaultValue>1</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM1</Name>
<Info>
<DefaultValue>2</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM2</Name>
<Info>
<DefaultValue>3</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM3</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
</Objects>
</Dictionary>
</Profile>
<Fmmu>Outputs</Fmmu>
<Fmmu>Inputs</Fmmu>
<Fmmu>MBoxState</Fmmu>
<Sm ControlByte="#x26" DefaultSize="512" Enable="1" StartAddress="#x1000">MBoxOut</Sm>
<Sm ControlByte="#x22" DefaultSize="512" Enable="1" StartAddress="#x1200">MBoxIn</Sm>
<Sm ControlByte="#x24" Enable="0" StartAddress="#x1400">Outputs</Sm>
<Sm ControlByte="#x20" Enable="0" StartAddress="#x1A00">Inputs</Sm>
<Mailbox DataLinkLayer="true">
<CoE CompleteAccess="false" PdoUpload="false" SdoInfo="true"/>
</Mailbox>
<Eeprom>
<ByteSize>256</ByteSize>
<ConfigData>0502030000000000</ConfigData>
<BootStrap>0010000200120002</BootStrap>
</Eeprom>
</Device>
</Devices>
</Descriptions>
</EtherCATInfo>

View File

@@ -0,0 +1,14 @@
describe("backup", function() {
beforeEach(function() {
});
it("should return false for button control", function() {
// arrange
const control = { type: 'button' };
// act
var result = isBackedUp(control);
// assert
expect(result).toEqual(false);
});
});

View File

@@ -0,0 +1,593 @@
describe("generators", function() {
describe("for default, empty project", function() {
var form;
var od;
var indexes;
beforeEach(function() {
form = buildMockFormHelper();
od = buildObjectDictionary(form);
indexes = getUsedIndexes(od);
});
it("esi_generator should generate expected code", function() {
// arrange
const dc = [];
// act
var result = esi_generator(form, od, indexes, dc);
// assert
const expectedesi =
`<?xml version="1.0" encoding="UTF-8"?>
<EtherCATInfo>
<Vendor>
<Id>0</Id>
<Name LcId="1033">ACME EtherCAT Devices</Name>
</Vendor>
<Descriptions>
<Groups>
<Group>
<Type>DigIn</Type>
<Name LcId="1033">Digital input</Name>
</Group>
</Groups>
<Devices>
<Device Physics="YY ">
<Type ProductCode="#xab123" RevisionNo="#x2">DigIn2000</Type>
<Name LcId="1033">2-channel Hypergalactic input superimpermanator</Name>
<GroupType>DigIn</GroupType>
<Profile>
<ProfileNo>5001</ProfileNo>
<AddInfo>0</AddInfo>
<Dictionary>
<DataTypes>
<DataType>
<Name>DT1018</Name>
<BitSize>144</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>1</SubIdx>
<Name>Vendor ID</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>2</SubIdx>
<Name>Product Code</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>48</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>3</SubIdx>
<Name>Revision Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>80</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<SubIdx>4</SubIdx>
<Name>Serial Number</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<BitOffs>112</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>DT1C00ARR</Name>
<BaseType>USINT</BaseType>
<BitSize>32</BitSize>
<ArrayInfo>
<LBound>1</LBound>
<Elements>4</Elements>
</ArrayInfo>
</DataType>
<DataType>
<Name>DT1C00</Name>
<BitSize>48</BitSize>
<SubItem>
<SubIdx>0</SubIdx>
<Name>Max SubIndex</Name>
<Type>USINT</Type>
<BitSize>8</BitSize>
<BitOffs>0</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
<SubItem>
<Name>Elements</Name>
<Type>DT1C00ARR</Type>
<BitSize>32</BitSize>
<BitOffs>16</BitOffs>
<Flags>
<Access>ro</Access>
</Flags>
</SubItem>
</DataType>
<DataType>
<Name>UDINT</Name>
<BitSize>32</BitSize>
</DataType>
<DataType>
<Name>STRING(47)</Name>
<BitSize>376</BitSize>
</DataType>
<DataType>
<Name>STRING(5)</Name>
<BitSize>40</BitSize>
</DataType>
<DataType>
<Name>USINT</Name>
<BitSize>8</BitSize>
</DataType>
</DataTypes>
<Objects>
<Object>
<Index>#x1000</Index>
<Name>Device Type</Name>
<Type>UDINT</Type>
<BitSize>32</BitSize>
<Info>
<DefaultValue>5001</DefaultValue>
</Info>
<Flags>
<Access>ro</Access>
<Category>m</Category>
</Flags>
</Object>
<Object>
<Index>#x1008</Index>
<Name>Device Name</Name>
<Type>STRING(47)</Type>
<BitSize>376</BitSize>
<Info>
<DefaultString>2-channel Hypergalactic input superimpermanator</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1009</Index>
<Name>Hardware Version</Name>
<Type>STRING(5)</Type>
<BitSize>40</BitSize>
<Info>
<DefaultString>0.0.1</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
<Category>o</Category>
</Flags>
</Object>
<Object>
<Index>#x100A</Index>
<Name>Software Version</Name>
<Type>STRING(5)</Type>
<BitSize>40</BitSize>
<Info>
<DefaultString>0.0.1</DefaultString>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1018</Index>
<Name>Identity Object</Name>
<Type>DT1018</Type>
<BitSize>144</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Vendor ID</Name>
<Info>
<DefaultValue>0</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Product Code</Name>
<Info>
<DefaultValue>700707</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Revision Number</Name>
<Info>
<DefaultValue>2</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Serial Number</Name>
<Info>
<DefaultValue>1</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
<Object>
<Index>#x1C00</Index>
<Name>Sync Manager Communication Type</Name>
<Type>DT1C00</Type>
<BitSize>48</BitSize>
<Info>
<SubItem>
<Name>Max SubIndex</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM0</Name>
<Info>
<DefaultValue>1</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM1</Name>
<Info>
<DefaultValue>2</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM2</Name>
<Info>
<DefaultValue>3</DefaultValue>
</Info>
</SubItem>
<SubItem>
<Name>Communications Type SM3</Name>
<Info>
<DefaultValue>4</DefaultValue>
</Info>
</SubItem>
</Info>
<Flags>
<Access>ro</Access>
</Flags>
</Object>
</Objects>
</Dictionary>
</Profile>
<Fmmu>Outputs</Fmmu>
<Fmmu>Inputs</Fmmu>
<Fmmu>MBoxState</Fmmu>
<Sm DefaultSize="512" StartAddress="#x1000" ControlByte="#x26" Enable="1">MBoxOut</Sm>
<Sm DefaultSize="512" StartAddress="#x1200" ControlByte="#x22" Enable="1">MBoxIn</Sm>
<Sm StartAddress="#x1600" ControlByte="#x24" Enable="0">Outputs</Sm>
<Sm StartAddress="#x1A00" ControlByte="#x20" Enable="0">Inputs</Sm>
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" PdoAssign="false" PdoConfig="false" PdoUpload="true" CompleteAccess="false" />
</Mailbox>
<Dc>
</Dc>
<Eeprom>
<ByteSize>2048</ByteSize>
<ConfigData>05060344640000</ConfigData>
</Eeprom>
</Device>
</Devices>
</Descriptions>
</EtherCATInfo>`;
console.log(result);
debugger;
expect(result.slice(9000)).toEqual(expectedesi.slice(9000));
});
it("hex_generator should generate config data", function() {
// arrange
// act
var result = hex_generator(form, true);
// assert
const configData = `05060344640000`;
expect(result).toEqual(configData);
});
it("ecat_options_generator should generate config data", function() {
// arrange
// act
var result = ecat_options_generator(form, od, indexes);
// assert
const ecat_options =
`#ifndef __ECAT_OPTIONS_H__
#define __ECAT_OPTIONS_H__
#define USE_FOE 0
#define USE_EOE 0
#define MBXSIZE 512
#define MBXSIZEBOOT 512
#define MBXBUFFERS 3
#define MBX0_sma 0x1000
#define MBX0_sml MBXSIZE
#define MBX0_sme MBX0_sma+MBX0_sml-1
#define MBX0_smc 0x26
#define MBX1_sma MBX0_sma+MBX0_sml
#define MBX1_sml MBXSIZE
#define MBX1_sme MBX1_sma+MBX1_sml-1
#define MBX1_smc 0x22
#define MBX0_sma_b 0x1000
#define MBX0_sml_b MBXSIZEBOOT
#define MBX0_sme_b MBX0_sma_b+MBX0_sml_b-1
#define MBX0_smc_b 0x26
#define MBX1_sma_b MBX0_sma_b+MBX0_sml_b
#define MBX1_sml_b MBXSIZEBOOT
#define MBX1_sme_b MBX1_sma_b+MBX1_sml_b-1
#define MBX1_smc_b 0x22
#define SM2_sma 0x1600
#define SM2_smc 0x24
#define SM2_act 1
#define SM3_sma 0x1A00
#define SM3_smc 0x20
#define SM3_act 1
#define MAX_MAPPINGS_SM2 0
#define MAX_MAPPINGS_SM3 0
#define MAX_RXPDO_SIZE 512
#define MAX_TXPDO_SIZE 512
#endif /* __ECAT_OPTIONS_H__ */
`;
expect(result).toEqual(ecat_options);
});
it("objectlist_generator should generate config data", function() {
// arrange
// act
var result = objectlist_generator(form, od, indexes);
// assert
const objectlist =
`#include "esc_coe.h"
#include "utypes.h"
#include <stddef.h>
static const char acName1000[] = "Device Type";
static const char acName1008[] = "Device Name";
static const char acName1009[] = "Hardware Version";
static const char acName100A[] = "Software Version";
static const char acName1018[] = "Identity Object";
static const char acName1018_00[] = "Max SubIndex";
static const char acName1018_01[] = "Vendor ID";
static const char acName1018_02[] = "Product Code";
static const char acName1018_03[] = "Revision Number";
static const char acName1018_04[] = "Serial Number";
static const char acName1C00[] = "Sync Manager Communication Type";
static const char acName1C00_00[] = "Max SubIndex";
static const char acName1C00_01[] = "Communications Type SM0";
static const char acName1C00_02[] = "Communications Type SM1";
static const char acName1C00_03[] = "Communications Type SM2";
static const char acName1C00_04[] = "Communications Type SM3";
const _objd SDO1000[] =
{
{0x0, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1000, 5001, NULL},
};
const _objd SDO1008[] =
{
{0x0, DTYPE_VISIBLE_STRING, 376, ATYPE_RO, acName1008, 0, "2-channel Hypergalactic input superimpermanator"},
};
const _objd SDO1009[] =
{
{0x0, DTYPE_VISIBLE_STRING, 40, ATYPE_RO, acName1009, 0, "0.0.1"},
};
const _objd SDO100A[] =
{
{0x0, DTYPE_VISIBLE_STRING, 40, ATYPE_RO, acName100A, 0, "0.0.1"},
};
const _objd SDO1018[] =
{
{0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1018_00, 4, NULL},
{0x01, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_01, 0, NULL},
{0x02, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_02, 700707, NULL},
{0x03, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_03, 2, NULL},
{0x04, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_04, 1, &Obj.serial},
};
const _objd SDO1C00[] =
{
{0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_00, 4, NULL},
{0x01, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_01, 1, NULL},
{0x02, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_02, 2, NULL},
{0x03, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_03, 3, NULL},
{0x04, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_04, 4, NULL},
};
const _objectlist SDOobjects[] =
{
{0x1000, OTYPE_VAR, 0, 0, acName1000, SDO1000},
{0x1008, OTYPE_VAR, 0, 0, acName1008, SDO1008},
{0x1009, OTYPE_VAR, 0, 0, acName1009, SDO1009},
{0x100A, OTYPE_VAR, 0, 0, acName100A, SDO100A},
{0x1018, OTYPE_RECORD, 4, 0, acName1018, SDO1018},
{0x1C00, OTYPE_ARRAY, 4, 0, acName1C00, SDO1C00},
{0xffff, 0xff, 0xff, 0xff, NULL, NULL}
};
`;
expect(result).toEqual(objectlist);
});
it("utypes_generator should generate expected code", function() {
// arrange
// act
var result = utypes_generator(form, od, indexes);
// assert
const expectedUtypes =
`#ifndef __UTYPES_H__
#define __UTYPES_H__
#include "cc.h"
/* Object dictionary storage */
typedef struct
{
/* Identity */
uint32_t serial;
} _Objects;
extern _Objects Obj;
#endif /* __UTYPES_H__ */
`;
expect(result).toEqual(expectedUtypes);
});
describe("hex_generator given SPImode", function () {
const testCases = [
{ mode: 0 },
{ mode: 1 },
{ mode: 2 },
{ mode: 3 },
];
testCases.forEach((test, index) => {
it(`SPImode ${test.mode} should return 05060${test.mode}44640000 (testcase: ${index + 1})`, () => {
// arrange
form.SPImode.value = test.mode;
// act
var result = hex_generator(form, true);
// assert
expect(result).toEqual(`05060${test.mode}44640000`);
});
});
});
});
describe("for AX58100 project", function() {
var form;
var od;
var indexes;
beforeEach(function() {
form = buildMockFormHelper();
form.ESC.value = SupportedESC.AX58100
od = buildObjectDictionary(form);
indexes = getUsedIndexes(od);
});
it("hex_generator should generate config data 050603446400000000001A000000", function() {
// arrange
// act
var result = hex_generator(form, true);
// assert
const configData = `050603446400000000001A000000`;
expect(result).toEqual(configData);
});
});
describe("for ET1100 project", function() {
var form;
var od;
var indexes;
beforeEach(function() {
form = buildMockFormHelper();
form.ESC.value = SupportedESC.ET1100
od = buildObjectDictionary(form);
indexes = getUsedIndexes(od);
});
it("hex_generator should generate config data 05060344640000", function() {
// arrange
// act
var result = hex_generator(form, true);
// assert
const configData = `05060344640000`;
expect(result).toEqual(configData);
});
});
describe("for LAN9252 project", function() {
var form;
var od;
var indexes;
beforeEach(function() {
form = buildMockFormHelper();
form.ESC.value = SupportedESC.LAN9252
od = buildObjectDictionary(form);
indexes = getUsedIndexes(od);
});
it("hex_generator should generate config data 80060344640000", function() {
// arrange
// act
var result = hex_generator(form, true);
// assert
const configData = `80060344640000`;
expect(result).toEqual(configData);
});
});
describe("for LAN9253_Beckhoff project", function() {
var form;
var od;
var indexes;
beforeEach(function() {
form = buildMockFormHelper();
form.ESC.value = SupportedESC.LAN9253_Beckhoff
od = buildObjectDictionary(form);
indexes = getUsedIndexes(od);
});
it("hex_generator should generate config data 0506034464000000000040C00000", function() {
// arrange
// act
var result = hex_generator(form, true);
// assert
const configData = `0506034464000000000040C00000`;
expect(result).toEqual(configData);
});
});
});

View File

@@ -0,0 +1,25 @@
var formMock = {};
function buildMockFormHelper(formValues = null) {
const defaultFormValues = getFormDefaultValues().form;
if (!formValues) {
formValues = defaultFormValues;
}
formMock = { }
Object.keys(defaultFormValues).forEach(formControlName => {
const formControl = {
name: formControlName,
};
setFormControlValue(formControl, formValues[formControlName]);
formMock[formControlName] = formControl;
});
return formMock;
}
function getForm() {
return formMock;
}

View File

@@ -0,0 +1,127 @@
/**
* SOES EEPROM generator
* Project backup save and restore
* This tool serves as:
- EtherCAT Slave Information XML + EEPROM binary generator
- SOES code generator
* Victor Sluiter 2013-2018
* Kuba Buda 2020-2021
*/
'use strict'
// ####################### Backup serialization + deserialization ####################### //
function isValidBackup(backup) {
if (!backup || !backup.form || !backup.od ) {
if (!confirm('Backup is incomplete or invalid, proceed anyway?')) {
return false;
}
}
return true;
}
function prepareBackupObject(form) {
const formValues = {};
if (form) {
Object.entries(form).forEach(formEntry => {
const formControl = formEntry[1]; // entry[0] is form control order number
if(isBackedUp(formControl) && formControl.value) {
formValues[formControl.name] = formControl.value;
};
});
}
const backup = {
form: formValues,
od: getObjDict(),
dc: _dc,
};
return backup;
}
function isBackedUp(formControl) {
return formControl.type != "button";
}
function loadBackup(backupObject, form) {
if (backupObject.od) {
setObjDictSection(sdo, backupObject.od.sdo);
setObjDictSection(txpdo, backupObject.od.txpdo);
setObjDictSection(rxpdo, backupObject.od.rxpdo);
}
if (backupObject.dc) {
_dc = backupObject.dc;
}
setFormValues(form, backupObject);
}
function setFormValues(form, backupObject) {
if (form) {
Object.entries(form).forEach(formEntry => {
const formControl = formEntry[1]; // entry[0] is index
const formControlValue = backupObject.form[formControl.name];
if (isBackedUp(formControl) && formControlValue) {
setFormControlValue(formControl, formControlValue);
};
});
}
}
function setFormControlValue(formControl, formControlValue) {
if (formControl.name.startsWith('CoeDetailsEnable')) {
if (formControlValue == true) {
formControl.checked = true;
} else {
}
} else {
formControl.value = formControlValue;
}
}
function prepareBackupFileContent(form) {
var backupObject = prepareBackupObject(form);
var backupFileContent = JSON.stringify(backupObject, null, 2); // pretty print
return backupFileContent;
}
// ####################### Backup using JSON file from filesystem ####################### //
// Localstorage limit is usually 5MB, super large object dictionaries on older browsers might be problematic
function downloadBackupFile(form) {
const backupFileContent = prepareBackupFileContent(form); // pretty print
downloadFile(backupFileContent, 'esi.json', 'text/json');
}
function restoreBackup(fileContent, form) {
var backup = JSON.parse(fileContent);
if (isValidBackup(backup)) {
loadBackup(backup, form);
reloadOD_Sections();
reloadSyncModes()
}
}
// ####################### Backup using browser localstorage ####################### //
/** persist OD and settings changes over page reload */
function saveLocalBackup(form) {
localStorage.etherCATeepromGeneratorBackup = prepareBackupFileContent(form);
}
function tryRestoreLocalBackup(form) {
if (localStorage.etherCATeepromGeneratorBackup) {
restoreBackup(localStorage.etherCATeepromGeneratorBackup, form);
}
}
function resetLocalBackup() {
if (localStorage.etherCATeepromGeneratorBackup) {
delete localStorage.etherCATeepromGeneratorBackup;
}
}

View File

@@ -0,0 +1,157 @@
/**
* SOES EEPROM generator
* Shared constants, data types
* This tool serves as:
- EtherCAT Slave Information XML + EEPROM binary generator
- SOES code generator
* Victor Sluiter 2013-2018
* Kuba Buda 2020-2021
*/
'use strict'
const automaticCodegen = true; // code is regenerated on every form change.
// no need to remember to generate before copying or downloading
// app is noticeably slower
// ####################### Constants, lookup tables ####################### //
/** CoE Object Types */
const OTYPE = {
VAR : 'VAR',
ARRAY : 'ARRAY',
RECORD: 'RECORD',
};
/** CoE Data Types */
const DTYPE = {
BOOLEAN : 'BOOLEAN',
INTEGER8 : 'INTEGER8',
INTEGER16 : 'INTEGER16',
INTEGER32 : 'INTEGER32',
UNSIGNED8 : 'UNSIGNED8',
UNSIGNED16 : 'UNSIGNED16',
UNSIGNED32 : 'UNSIGNED32',
REAL32 : 'REAL32',
VISIBLE_STRING : 'VISIBLE_STRING',
/* TODO implement missing less common types */
// OCTET_STRING : 'OCTET_STRING',
// UNICODE_STRING : 'UNICODE_STRING',
// INTEGER24 : 'INTEGER24',
// UNSIGNED24 : 'UNSIGNED24',
// INTEGER64 : 'INTEGER64',
// UNSIGNED64 : 'UNSIGNED64',
// REAL64 : 'REAL64',
// PDO_MAPPING : 'PDO_MAPPING',
};
/** Data types bitsize as used in objectlist.c */
const dtype_bitsize = {
'BOOLEAN' : 1,
'INTEGER8' : 8,
'INTEGER16' : 16,
'INTEGER32' : 32,
'UNSIGNED8' : 8,
'UNSIGNED16' : 16,
'UNSIGNED32' : 32,
'REAL32' : 32,
'VISIBLE_STRING' : 8,
};
const booleanPaddingBitsize = 7;
/** ESI XML data type */
const ESI_DT = {
'BOOLEAN': { name: 'BOOL', bitsize: 1, ctype: 'uint8_t' },
'INTEGER8': { name: 'SINT', bitsize: 8, ctype: 'int8_t' },
'INTEGER16': { name: 'INT', bitsize: 16, ctype: 'int16_t' },
'INTEGER32': { name: 'DINT', bitsize: 32, ctype: 'int32_t' },
'UNSIGNED8': { name: 'USINT', bitsize: 8, ctype: 'uint8_t' },
'UNSIGNED16': { name: 'UINT', bitsize: 16, ctype: 'uint16_t' },
'UNSIGNED32': { name: 'UDINT', bitsize: 32, ctype: 'uint32_t' },
'REAL32': { name: 'REAL', bitsize: 32, ctype: 'float' }, // TODO check C type name
'VISIBLE_STRING': { name: 'STRING', bitsize: 8, ctype: 'char *' }, // TODO check C type name
};
/** These are required by minimal CiA 301 device */
const SDO_category = {
'1000': 'm',
'1009': 'o',
};
// ####################### Object Dictionary building ####################### //
/**
* Returns Object Dictionaty stub with mandatory objects.
* OD index is hexadecimal value without '0x' prefix
*/
function getMandatoryObjects() {
const OD = {
'1000': { otype: OTYPE.VAR, dtype: DTYPE.UNSIGNED32, name: 'Device Type', value: 0x1389 },
'1008': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Device Name', data: '' },
'1009': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Hardware Version', data: '' },
'100A': { otype: OTYPE.VAR, dtype: DTYPE.VISIBLE_STRING, name: 'Software Version', data: '' },
'1018': { otype: OTYPE.RECORD, name: 'Identity Object', items: [
{ name: 'Max SubIndex' },
{ name: 'Vendor ID', dtype: DTYPE.UNSIGNED32, value: 600 },
{ name: 'Product Code', dtype: DTYPE.UNSIGNED32 },
{ name: 'Revision Number', dtype: DTYPE.UNSIGNED32 },
{ name: 'Serial Number', dtype: DTYPE.UNSIGNED32, data: '&Obj.serial' },
]},
'1C00': { otype: OTYPE.ARRAY, dtype: DTYPE.UNSIGNED8, name: 'Sync Manager Communication Type', items: [
{ name: 'Max SubIndex' },
{ name: 'Communications Type SM0', value: 1 },
{ name: 'Communications Type SM1', value: 2 },
{ name: 'Communications Type SM2', value: 3 },
{ name: 'Communications Type SM3', value: 4 },
]},
};
return OD;
};
const sdo = 'sdo';
const txpdo = 'txpdo';
const rxpdo = 'rxpdo';
/** EtherCAT Slave Chips that are supported by this web tool */
const SupportedESC = {
AX58100: 'AX58100',
ET1100: 'ET1100',
LAN9252: 'LAN9252',
LAN9253_Beckhoff: 'LAN9253 Beckhoff',
};
/** ESCs that are using reserved bytes for configuration, for example AX58100 configdata reaches 0x0A byte */
const configOnReservedBytes = [SupportedESC.AX58100, SupportedESC.LAN9253_Beckhoff];
//** Form default values */
function getFormDefaultValues() {
return {form: {
VendorName: "ACME EtherCAT Devices",
VendorID: "0x000",
ProductCode: "0x00ab123",
ProfileNo: "5001",
RevisionNumber: "0x002",
SerialNumber: "0x001",
HWversion: "0.0.1",
SWversion: "0.0.1",
EEPROMsize: "2048",
RxMailboxOffset: "0x1000",
TxMailboxOffset: "0x1200",
MailboxSize: "512",
SM2Offset: "0x1600",
SM3Offset: "0x1A00",
TextGroupType: "DigIn",
TextGroupName5: "Digital input",
ImageName: "IMGCBY",
TextDeviceType: "DigIn2000",
TextDeviceName: "2-channel Hypergalactic input superimpermanator",
Port0Physical: "Y",
Port1Physical: "Y",
Port2Physical: " ",
Port3Physical: " ",
ESC: SupportedESC.ET1100,
SPImode: "3",
CoeDetailsEnableSDO: true,
CoeDetailsEnableSDOInfo: true,
CoeDetailsEnablePDOAssign: false,
CoeDetailsEnablePDOConfiguration: false,
CoeDetailsEnableUploadAtStartup: true,
CoeDetailsEnableSDOCompleteAccess: false,
}};
};

View File

@@ -0,0 +1,89 @@
/**
* SOES EEPROM generator
* Files input and output
* This tool serves as:
- EtherCAT Slave Information XML + EEPROM binary generator
- SOES code generator
* Victor Sluiter 2013-2018
* Kuba Buda 2020-2021
*/
'use strict'
// ####################### File operations ####################### //
/** save file in local filesystem, by downloading from browser */
function downloadFile(content, fileName, contentType) {
var a = document.createElement("a");
var file = new Blob([content], {type: contentType});
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
// a element will be garbage collected, no need to cleanup
}
/** reads saved project from file user opened */
function readFile(e) {
var file = e.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(e) {
onRestoreComplete(e.target.result);
}
reader.readAsText(file);
}
/** takes bytes array, returns Intel Hex as string */
function toIntelHex(record) {
var hex = "";
const bytes_per_rule = 32;
const rulesTotalCount = record.length/bytes_per_rule;
for (var rulenumber = 0 ; rulenumber < (rulesTotalCount); rulenumber++)
{
const sliceStart = rulenumber*bytes_per_rule;
const sliceEnd = bytes_per_rule + (rulenumber * bytes_per_rule);
const recordSlice = record.slice(sliceStart, sliceEnd);
hex += CreateiHexRule(bytes_per_rule, rulenumber, recordSlice);
}
//end of file marker
hex += ':00000001FF';
return hex.toUpperCase();
function CreateiHexRule(bytes_per_rule, rulenumber, record)
{
var record_type_datarecord = '00';
var rule = ':'+ bytes_per_rule.toString(16).slice(-2) + generate_hex_address(rulenumber*bytes_per_rule) + record_type_datarecord;
for(var byteposition = 0; byteposition < bytes_per_rule ; byteposition++)
{
var byte = record[byteposition].toString(16).slice(-2); // convert to hexadecimal, crop to last 2 digits
if(byte.length < 2)
byte = '0' + byte; //minimal field width = 2 characters.
rule += byte;
}
var checksum = 0;
for(var rule_pos = 0 ; rule_pos < (rule.length-1)/2 ; rule_pos++)
{
var byte = parseInt(rule.slice(1+(2*rule_pos), 3+(2*rule_pos)),16);
checksum += byte;
}
checksum %= 0x100; //leave last byte
checksum = 0x100-checksum; //two's complement
rule += checksum.toString(16).slice(-2) + '\n';
return rule;
}
/** takex number, returns its hexadecimal value padded/trimmed to 4 digits */
function generate_hex_address(number)
{
//convert to hexadecimal string
var output = number.toString(16);
//take care that 4 characters are present
while(output.length<4)
{
output ='0' + output;
}
//return 4 characters, prevents overflow
return output.slice(-4);
}
}

View File

@@ -0,0 +1,330 @@
/**
* SOES EEPROM generator
* EEPROM .bin / .hex 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'
// ####################### EEPROM generating ####################### //
function hex_generator(form, stringOnly=false)
{
//WORD ADDRESS 0-7
var record = getConfigDataBytes(form);
if (stringOnly) { return getConfigDataString(record, form.ESC.value); }
/** Takes form, returns config data:
* first 16 bytes (8 words) with check sum */
function getConfigDataBytes(form) {
const recordLength = parseInt(form.EEPROMsize.value);
var record = new Uint8Array(recordLength);
record.fill(0xFF);
//Start of EEPROM contents; A lot of information can be found in 5.4 of ETG1000.6
let pdiControl = 0x05;
const spiMode = parseInt(form.SPImode.value); // valid values ara 0, 1, 2 or 3
let reserved_0x05 = 0x0000;
switch(form.ESC.value) {
case SupportedESC.AX58100:
reserved_0x05 = 0x001A; // enable IO for SPI driver on AX58100:
// Write 0x1A value (INT edge pulse length, 8 mA Control + IO 9:0 Drive Select) to 0x0A (Host Interface Extend Setting and Drive Strength
break;
case SupportedESC.LAN9252:
pdiControl = 0x80;
break;
case SupportedESC.LAN9253_Beckhoff:
reserved_0x05 = 0xC040; // enable ERRLED, STATE_RUNLED and MI Write
// in ASIC CONFIGURATION REGISTER: 0142h-0143h (refer to DS00003421A-page 268)
break;
default:
break;
}
//WORD ADDRESS 0-7
writeEEPROMbyte_byteaddress(pdiControl, 0, record); //PDI control: SPI slave (mapped to register 0x0140)
writeEEPROMbyte_byteaddress(0x06, 1, record); //ESC configuration: Distributed clocks Sync Out and Latch In enabled (mapped register 0x0141)
writeEEPROMbyte_byteaddress(spiMode, 2, record); //SPI mode (mapped to register 0x0150)
writeEEPROMbyte_byteaddress(0x44, 3, record); //SYNC /LATCH configuration (mapped to 0x0151). Make both Syncs output
writeEEPROMword_wordaddress(0x0064, 2, record); //Syncsignal Pulselenght in 10ns units(mapped to 0x0982:0x0983)
writeEEPROMword_wordaddress(0x00, 3, record); //Extended PDI configuration (none for SPI slave)(0x0152:0x0153)
writeEEPROMword_wordaddress(0x00, 4, record); //Configured Station Alias (0x0012:0x0013)
writeEEPROMword_wordaddress(reserved_0x05, 5, record); //Reserved, 0 (when not AX58100)
writeEEPROMword_wordaddress(0, 6, record); //Reserved, 0
const crc = FindCRC(record, 14);
writeEEPROMword_wordaddress(crc, 7, record); //CRC
return record;
}
//WORD ADDRESS 8-15
writeEEPROMDword_wordaddress(parseInt(form.VendorID.value),8,record); //CoE 0x1018:01
writeEEPROMDword_wordaddress(parseInt(form.ProductCode.value),10,record); //CoE 0x1018:02
writeEEPROMDword_wordaddress(parseInt(form.RevisionNumber.value),12,record);//CoE 0x1018:03
writeEEPROMDword_wordaddress(parseInt(form.SerialNumber.value),14,record); //CoE 0x1018:04
//WORD ADDRESS 16-23
writeEEPROMword_wordaddress(0,16,record); //Execution Delay Time; units?
writeEEPROMword_wordaddress(0,17,record); //Port0 Delay Time; units?
writeEEPROMword_wordaddress(0,18,record); //Port1 Delay Time; units?
writeEEPROMword_wordaddress(0,19,record); //Reserved, zero
writeEEPROMword_wordaddress(0,20,record); //Bootstrap Rx mailbox offset //Bootstrap not supported
writeEEPROMword_wordaddress(0,21,record); //Bootstrap Rx mailbox size
writeEEPROMword_wordaddress(0,22,record); //Bootstrap Tx mailbox offset
writeEEPROMword_wordaddress(0,23,record); //Bootstrap Tx mailbox size
//WORD ADDRESS 24-...
writeEEPROMword_wordaddress(parseInt(form.RxMailboxOffset.value),24,record); //Standard Rx mailbox offset
writeEEPROMword_wordaddress(parseInt(form.MailboxSize.value),25,record); //Standard Rx mailbox size
writeEEPROMword_wordaddress(parseInt(form.TxMailboxOffset.value),26,record); //Standard Tx mailbox offset
writeEEPROMword_wordaddress(parseInt(form.MailboxSize.value),27,record); //Standard Tx mailbox size
writeEEPROMword_wordaddress(0x04,28,record); //CoE protocol, see Table18 in ETG1000.6
for (var count = 29; count <= 61; count++) { //fill reserved area with zeroes
writeEEPROMword_wordaddress(0,count,record);
}
writeEEPROMword_wordaddress((Math.floor(parseInt(form.EEPROMsize.value)/128))-1,62,record); //EEPROM size
writeEEPROMword_wordaddress(1,63,record); //Version
////////////////////////////////////
/// Vendor Specific Info //
////////////////////////////////////
//Strings
var array_of_strings = [form.TextDeviceType.value, form.TextGroupType.value, form.ImageName.value, form.TextDeviceName.value];
var offset = 0;
offset = writeEEPROMstrings(record, 0x80, array_of_strings); //See ETG1000.6 Table20
//General info
offset = writeEEPROMgeneral_settings(form,offset,record); //See ETG1000.6 Table21
//FMMU
offset = writeFMMU(form,offset, record); //see Table 22 ETG1000.6
//SyncManagers
offset = writeSyncManagers(form, offset, record); //See Table 23 ETG1000.6
//End of EEPROM contents
const eepromSize = getForm().EEPROMsize.value;
return record;
/** See ETG1000.6 Table20 for Category string */
function writeEEPROMstrings(record, offset, a_strings)
{
var number_of_strings = a_strings.length;
var total_string_data_length = 0;
var length_is_even;
for(var strcounter = 0; strcounter < number_of_strings ; strcounter++)
{
total_string_data_length += a_strings[strcounter].length //add length of strings
}
total_string_data_length += number_of_strings; //for each string a byte is needed to indicate the length
total_string_data_length += 1; //for byte to give 'number of strings'
if(total_string_data_length %2) //if length is even (ends at word boundary)
length_is_even = false;
else
length_is_even = true;
writeEEPROMword_wordaddress(0x000A, offset/2, record); //Type: STRING
writeEEPROMword_wordaddress(Math.ceil(total_string_data_length/2), (offset/2) + 1, record); //write length of complete package
offset += 4; //2 words written
writeEEPROMbyte_byteaddress(number_of_strings, offset++, record);
for(var strcounter = 0; strcounter < number_of_strings ; strcounter++)
{
writeEEPROMbyte_byteaddress(a_strings[strcounter].length, offset++, record);
for(var charcounter = 0 ; charcounter < a_strings[strcounter].length ; charcounter++)
{
writeEEPROMbyte_byteaddress(a_strings[strcounter].charCodeAt(charcounter), offset++, record);
}
}
if(length_is_even == false)
{
writeEEPROMbyte_byteaddress(0, offset++, record);
}
return offset;
}
/** See ETG1000.6 Table21 */
function writeEEPROMgeneral_settings(form,offset,record)
{
const General_category = 0x1E; // value: 30d
const categorysize = 0x10;
//Clear memory region
for(let wordcount = 0; wordcount < categorysize + 2; wordcount++) {
writeEEPROMword_wordaddress(0, (offset/2) + wordcount, record);
}
//write code 30, 'General type'. See ETG1000.6, Table 19
writeEEPROMword_wordaddress(General_category, offset/2, record);
//write length of General Category data
writeEEPROMword_wordaddress(categorysize, 1+(offset/2), record);
offset +=4;
writeEEPROMbyte_byteaddress(2,offset++,record);//index to string for Group Info
writeEEPROMbyte_byteaddress(3,offset++,record);//index to string for Image Name
writeEEPROMbyte_byteaddress(1,offset++,record);//index to string for Device Order Number
writeEEPROMbyte_byteaddress(4,offset++,record);//index to string for Device Name Information
offset++; //byte 4 is reserved
writeEEPROMbyte_byteaddress(getCOEdetails(form),offset++,record);//CoE Details
writeEEPROMbyte_byteaddress(0,offset++,record); //Enable FoE
writeEEPROMbyte_byteaddress(0,offset++,record); //Enable EoE
writeEEPROMbyte_byteaddress(0,offset++,record); //reserved
writeEEPROMbyte_byteaddress(0,offset++,record); //reserved
writeEEPROMbyte_byteaddress(0,offset++,record); //reserved
writeEEPROMbyte_byteaddress(0,offset++,record); //flags (Bit0: Enable SafeOp, Bit1: Enable notLRW
writeEEPROMword_wordaddress(0x0000, offset/2, record); //current consumption in mA
offset += 2;
writeEEPROMword_wordaddress(0x0000, offset/2, record); //2 pad bytes
offset += 2;
writeEEPROMword_wordaddress(getPhysicalPort(form), offset/2, record);
offset += 2;
offset += 14; //14 pad bytes
return offset;
}
/** See ETG1000.6 Table 22 */
function writeFMMU(form, offset, record)
{
const FMMU_category = 0x28 // 40d
writeEEPROMword_wordaddress(FMMU_category,offset/2,record);
offset += 2;
const length = 2 //length = 2 word = 4bytes: 3 FMMU's + padding
//length = 1 word = 2bytes: 2 FMMU's.
writeEEPROMword_wordaddress(length, offset/2, record);
offset += 2;
writeEEPROMbyte_byteaddress(1, offset++, record); //FMMU0 used for Outputs; see Table 22 ETG1000.6
writeEEPROMbyte_byteaddress(2, offset++, record); //FMMU1 used for Inputs; see Table 22 ETG1000.6
writeEEPROMbyte_byteaddress(3, offset++, record); //FMMU2 used for Mailbox State
writeEEPROMbyte_byteaddress(0, offset++, record); //padding, disable FMMU4 if exists
return offset;
}
/** See Table 23 ETG1000.6 */
function writeSyncManagers(form, offset, record)
{
const SyncManager_category = 0x29 // 41d
writeEEPROMword_wordaddress(SyncManager_category, offset/2, record); //SyncManager
offset += 2;
writeEEPROMword_wordaddress(0x10, offset/2, record); //size of structure category
offset += 2;
//SM0
writeEEPROMword_wordaddress(parseInt(form.RxMailboxOffset.value),offset/2, record); //Physical start address
offset += 2;
writeEEPROMword_wordaddress(parseInt(form.MailboxSize.value),offset/2, record); //Physical size
offset += 2;
writeEEPROMbyte_byteaddress(0x26,offset++, record); //Mode of operation
writeEEPROMbyte_byteaddress(0,offset++, record); //don't care
writeEEPROMbyte_byteaddress(1,offset++, record); //Enable Syncmanager; bit0: enable, bit 1: fixed content, bit 2: virtual SyncManager, bit 3: Op Only
writeEEPROMbyte_byteaddress(1,offset++, record); //SyncManagerType; 0: not used, 1: Mbx out, 2: Mbx In, 3: PDO, 4: PDI
//SM1
writeEEPROMword_wordaddress(parseInt(form.TxMailboxOffset.value),offset/2, record); //Physical start address
offset += 2;
writeEEPROMword_wordaddress(parseInt(form.MailboxSize.value),offset/2, record); //Physical size
offset += 2;
writeEEPROMbyte_byteaddress(0x22,offset++, record); //Mode of operation
writeEEPROMbyte_byteaddress(0,offset++, record); //don't care
writeEEPROMbyte_byteaddress(1,offset++, record); //Enable Syncmanager; bit0: enable, bit 1: fixed content, bit 2: virtual SyncManager, bit 3: Op Only
writeEEPROMbyte_byteaddress(2,offset++, record); //SyncManagerType; 0: not used, 1: Mbx out, 2: Mbx In, 3: PDO, 4: PDI
//SM2
writeEEPROMword_wordaddress(parseInt(form.SM2Offset.value),offset/2, record); //Physical start address
offset += 2;
writeEEPROMword_wordaddress(0,offset/2, record); //Physical size
offset += 2;
writeEEPROMbyte_byteaddress(0x24,offset++, record); //Mode of operation
writeEEPROMbyte_byteaddress(0,offset++, record); //don't care
writeEEPROMbyte_byteaddress(1,offset++, record); //Enable Syncmanager; bit0: enable, bit 1: fixed content, bit 2: virtual SyncManager, bit 3: Op Only
writeEEPROMbyte_byteaddress(3,offset++, record); //SyncManagerType; 0: not used, 1: Mbx out, 2: Mbx In, 3: PDO, 4: PDI
//SM3
writeEEPROMword_wordaddress(parseInt(form.SM3Offset.value),offset/2, record); //Physical start address
offset += 2;
writeEEPROMword_wordaddress(0,offset/2, record); //Physical size
offset += 2;
writeEEPROMbyte_byteaddress(0x20,offset++, record); //Mode of operation
writeEEPROMbyte_byteaddress(0,offset++, record); //don't care
writeEEPROMbyte_byteaddress(1,offset++, record); //Enable Syncmanager; bit0: enable, bit 1: fixed content, bit 2: virtual SyncManager, bit 3: Op Only
writeEEPROMbyte_byteaddress(4,offset++, record); //SyncManagerType; 0: not used, 1: Mbx out, 2: Mbx In, 3: PDO, 4: PDI
return offset;
}
function getCOEdetails(form)
{
let coedetails = 0;
if(form.CoeDetailsEnableSDO.checked) coedetails |= 0x01; //Enable SDO
if(form.CoeDetailsEnableSDOInfo.checked) coedetails |= 0x02; //Enable SDO Info
if(form.CoeDetailsEnablePDOAssign.checked) coedetails |= 0x04; //Enable PDO Assign
if(form.CoeDetailsEnablePDOConfiguration.checked) coedetails |= 0x08; //Enable PDO Configuration
if(form.CoeDetailsEnableUploadAtStartup.checked) coedetails |= 0x10; //Enable Upload at startup
if(form.CoeDetailsEnableSDOCompleteAccess.checked) coedetails |= 0x20; //Enable SDO complete access
return coedetails;
}
/** ETG1000.6 Table 21 */
function getPhysicalPort(form)
{
let portinfo = 0;
let physicals = [form.Port3Physical.value, form.Port2Physical.value, form.Port1Physical.value, form.Port0Physical.value];
for (var physicalcounter = 0; physicalcounter < physicals.length ; physicalcounter++)
{
portinfo = (portinfo << 4); //shift previous result
switch(physicals[physicalcounter])
{
case 'Y':
case 'H':
portinfo |= 0x01; //MII
break;
case 'K':
portinfo |= 0x03; //EBUS
break;
default:
portinfo |= 0; //No connection
}
}
return portinfo;
}
/** computes crc value */
function FindCRC(data,datalen) {
var i,j;
var c;
var CRC=0xFF;
var genPoly = 0x07;
for (j=0; j<datalen; j++)
{
c = data[j];
CRC ^= c;
for(i = 0; i<8; i++)
if(CRC & 0x80 )
CRC = (CRC << 1) ^ genPoly;
else
CRC <<= 1;
CRC &= 0xff;
}
return CRC;
}
function writeEEPROMbyte_byteaddress(byte, address, record)
{
record[address] = byte;
}
function writeEEPROMbyte_wordaddress(byte, address, record)
{
record[address*2] = byte;
}
function writeEEPROMword_wordaddress(word, address, record)
{//little endian word storage!
record[ address*2 ] = word&0xFF;
record[1 + (address*2)] = (word>>8) & 0xFF;
}
function writeEEPROMDword_wordaddress(word, address, record)
{//little endian word storage!
record[ address*2 ] = word&0xFF;
record[1 + (address*2)] = (word>>8) & 0xFF;
record[2 + (address*2)] = (word>>16) & 0xFF;
record[3 + (address*2)] = (word>>24) & 0xFF;
}
/** takes bytes array and count, returns ConfigData string */
function getConfigDataString(record, esc) {
const configdata_bytecount = new Set(configOnReservedBytes).has(esc) ? 14 : 7;
var configdata = '';
for (var bytecount = 0; bytecount < configdata_bytecount; bytecount++) {
configdata += (record[bytecount] + 0x100).toString(16).slice(-2).toUpperCase();
}
return configdata;
}
}

View File

@@ -0,0 +1,95 @@
/**
* SOES EEPROM generator
* ecat_options.h 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'
// ####################### ecat_options.h generation ####################### //
function ecat_options_generator(form, od, indexes)
{
let ecat_options = '#ifndef __ECAT_OPTIONS_H__\n#define __ECAT_OPTIONS_H__\n\n#define USE_FOE 0\n#define USE_EOE 0\n\n';
//Mailbox size
ecat_options += '#define MBXSIZE ' + parseInt(form.MailboxSize.value).toString()
+ '\n#define MBXSIZEBOOT ' + parseInt(form.MailboxSize.value).toString()
+ '\n#define MBXBUFFERS 3\n\n';
//Mailbox 0 Config
ecat_options += `#define MBX0_sma 0x${indexToString(form.RxMailboxOffset.value)}`
+ '\n#define MBX0_sml MBXSIZE'
+ '\n#define MBX0_sme MBX0_sma+MBX0_sml-1'
+ '\n#define MBX0_smc 0x26\n';
//Mailbox 1 Config
ecat_options += `#define MBX1_sma MBX0_sma+MBX0_sml` //'0x${indexToString(form.TxMailboxOffset.value)}`;
+ '\n#define MBX1_sml MBXSIZE'
+ '\n#define MBX1_sme MBX1_sma+MBX1_sml-1'
+ '\n#define MBX1_smc 0x22\n\n';
// Mailbox boot configuration
ecat_options += `#define MBX0_sma_b 0x${indexToString(form.RxMailboxOffset.value)}`
+ '\n#define MBX0_sml_b MBXSIZEBOOT'
+ '\n#define MBX0_sme_b MBX0_sma_b+MBX0_sml_b-1'
+ '\n#define MBX0_smc_b 0x26\n';
ecat_options += `#define MBX1_sma_b MBX0_sma_b+MBX0_sml_b` //'0x${indexToString(form.TxMailboxOffset.value)}`;
+ '\n#define MBX1_sml_b MBXSIZEBOOT'
+ '\n#define MBX1_sme_b MBX1_sma_b+MBX1_sml_b-1'
+ '\n#define MBX1_smc_b 0x22\n\n';
//SyncManager2 Config
ecat_options += `#define SM2_sma 0x${indexToString(form.SM2Offset.value)}`
+ '\n#define SM2_smc 0x24'
+ '\n#define SM2_act 1\n';
//SyncManager3 Config
ecat_options += `#define SM3_sma 0x${indexToString(form.SM3Offset.value)}`
+ '\n#define SM3_smc 0x20'
+ '\n#define SM3_act 1\n\n';
// Mappings config
const MAX_MAPPINGS_SM2 = getMaxMappings(od, indexes, rxpdo);
const MAX_MAPPINGS_SM3 = getMaxMappings(od, indexes, txpdo);
ecat_options += `#define MAX_MAPPINGS_SM2 ${MAX_MAPPINGS_SM2}`
+ `\n#define MAX_MAPPINGS_SM3 ${MAX_MAPPINGS_SM3}\n\n`
// PDO buffer config
ecat_options += '#define MAX_RXPDO_SIZE 512' // TODO calculate based on offset, size
+ '\n#define MAX_TXPDO_SIZE 512\n\n'
+ '#endif /* __ECAT_OPTIONS_H__ */\n';
return ecat_options;
function getMaxMappings(od, indexes, pdoName) {
let result = 0;
indexes.forEach(index => {
const objd = od[index];
if(objd.pdo_mappings) {
if(objd.items) {
objd.items.slice(1).forEach(subitem => {
objd.pdo_mappings.forEach(mapping => {
if (mapping == pdoName) {
++result;
if (subitem.dtype == DTYPE.BOOLEAN) {
++result; // boolean padding is mapping too
// TODO handle array of booleans
}
}
});
});
} else if(objd.pdo_mappings) {
objd.pdo_mappings.forEach(mapping => {
if (mapping == pdoName) {
++result;
if (objd.dtype == DTYPE.BOOLEAN) {
++result; // boolean padding is mapping too
}
}
});
};
};
});
return result;
}
}

View File

@@ -0,0 +1,410 @@
/**
* 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 =`<?xml version="1.0" encoding="UTF-8"?>\n<EtherCATInfo>\n <Vendor>\n <Id>${parseInt(form.VendorID.value).toString()}</Id>\n`;
//VendorName
esi += ` <Name LcId="1033">${form.VendorName.value}</Name>\n </Vendor>\n <Descriptions>\n`;
//Groups
esi += ` <Groups>\n <Group>\n <Type>${form.TextGroupType.value}</Type>\n <Name LcId="1033">${form.TextGroupName5.value}</Name>\n </Group>\n </Groups>\n <Devices>\n`;
//Physics
esi += ` <Device Physics="${form.Port0Physical.value + form.Port1Physical.value + form.Port2Physical.value || + form.Port3Physical.value}">\n <Type ProductCode="#x${parseInt(form.ProductCode.value).toString(16)}" RevisionNo="#x${parseInt(form.RevisionNumber.value).toString(16)}">${form.TextDeviceType.value}</Type>\n`;
//Add Name info
esi += ` <Name LcId="1033">${form.TextDeviceName.value}</Name>\n`;
//Add in between
esi += ` <GroupType>${form.TextGroupType.value}</GroupType>\n`;
//Add profile
esi += ` <Profile>\n <ProfileNo>${form.ProfileNo.value}</ProfileNo>\n <AddInfo>0</AddInfo>\n <Dictionary>\n <DataTypes>`;
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 <DataType>`;
let flags = `\n <Access>ro</Access>`; // 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 <Name>${dtName}ARR</Name>\n <BaseType>${esi_type.name}</BaseType>\n <BitSize>${arr_bitsize}</BitSize>`;
result += `\n <ArrayInfo>\n <LBound>1</LBound>\n <Elements>${objd.items.length - 1}</Elements>\n </ArrayInfo>`;
result += `\n </DataType>`;
result += `\n <DataType>`;
}
result += `\n <Name>${dtName}</Name>\n <BitSize>${bitsize}</BitSize>`;
result += `\n <SubItem>\n <SubIdx>0</SubIdx>\n <Name>Max SubIndex</Name>\n <Type>USINT</Type>`
+ `\n <BitSize>8</BitSize>\n <BitOffs>0</BitOffs>\n <Flags>${flags}\n </Flags>\n </SubItem>`;
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 <SubItem>\n <Name>Elements</Name>\n <Type>${dtName}ARR</Type>\n <BitSize>${arr_bitsize}</BitSize>`
+`\n <BitOffs>16</BitOffs>\n <Flags>${flags}\n </Flags>\n </SubItem>`;
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 <SubItem>\n <SubIdx>${subindex}</SubIdx>\n <Name>${subitem.name}</Name>`
+ `\n <Type>${subitem_dtype.name}</Type>\n <BitSize>${subitem_bitsize}</BitSize>\n <BitOffs>${bits_offset}</BitOffs>`
+ `\n <Flags>${subitemFlags}\n </Flags>`
+ `\n </SubItem>`;
bits_offset += subitem_bitsize;
}
subindex++;
});
break;
} default: {
alert(`Object ${index} "${objd.name}" has unexpected OTYPE ${objd.otype}`);
alert;
}}
result += `\n </DataType>`;
}
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${modifier}>${access}</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 <DataType>`;
esi += `\n <Name>${variableType[0]}</Name>\n <BitSize>${variableType[1]}</BitSize>`;
esi += `\n </DataType>`;
});
esi += `\n </DataTypes>\n <Objects>`;
// Add objects dictionary
function addDictionaryObject(od, index) {
const objd = od[index];
const el_dtype = esiDtName(objd, index);
const bitsize = esiBitsize(objd);
let result = `\n <Object>\n <Index>#x${index}</Index>\n <Name>${objd.name}</Name>\n <Type>${el_dtype}</Type>\n <BitSize>${bitsize}</BitSize>\n <Info>`;
if (objd.data) {
if (objd.dtype == DTYPE.VISIBLE_STRING) {
result += `\n <DefaultString>${objd.data}</DefaultString>`;
}
}
if (objd.value) {
result += `\n <DefaultValue>${toEsiHexValue(objd.value)}</DefaultValue>`;
}
//Add object subitems for complex types
if (objd.items) {
result += addDictionaryObjectSubitems(objd.items);
}
var flags = `\n <Access>ro</Access>`;
if (objd.otype == OTYPE.VAR) {
flags += getPdoMappingFlags(objd);
}
if (SDO_category[index]) {
flags += `\n <Category>${SDO_category[index]}</Category>`;
}
result += `\n </Info>\n <Flags>${flags}\n </Flags>\n </Object>`;
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 <SubItem>\n <Name>${subitem.name}</Name>\n <Info>\n <DefaultValue>${toEsiHexValue(defaultValue)}</DefaultValue>\n </Info>\n </SubItem>`;
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 </Objects>\n </Dictionary>\n </Profile>\n <Fmmu>Outputs</Fmmu>\n <Fmmu>Inputs</Fmmu>\n <Fmmu>MBoxState</Fmmu>\n`;
//Add Rxmailbox sizes
esi += ` <Sm DefaultSize="${parseInt(form.MailboxSize.value).toString(10)}" StartAddress="#x${indexToString(form.RxMailboxOffset.value)}" ControlByte="#x26" Enable="1">MBoxOut</Sm>\n`;
//Add Txmailbox sizes
esi += ` <Sm DefaultSize="${parseInt(form.MailboxSize.value).toString(10)}" StartAddress="#x${indexToString(form.TxMailboxOffset.value)}" ControlByte="#x22" Enable="1">MBoxIn</Sm>\n`;
//Add SM2
esi += ` <Sm StartAddress="#x${indexToString(form.SM2Offset.value)}" ControlByte="#x24" Enable="${is_rxpdo ? 1 : 0}">Outputs</Sm>\n`;
//Add SM3
esi += ` <Sm StartAddress="#x${indexToString(form.SM3Offset.value)}" ControlByte="#x20" Enable="${is_txpdo ? 1 : 0}">Inputs</Sm>\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 += ` <Mailbox DataLinkLayer="true">\n <CoE ${getCoEString(form)}/>\n </Mailbox>\n`;
//Add DCs
esi += getEsiDCsection(dc);
//Add EEPROM
const configdata = hex_generator(form, true);
esi +=` <Eeprom>\n <ByteSize>${parseInt(form.EEPROMsize.value)}</ByteSize>\n <ConfigData>${configdata}</ConfigData>\n </Eeprom>\n`;
//Close all items
esi +=` </Device>\n </Devices>\n </Descriptions>\n</EtherCATInfo>`;
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 <Index>#x${memoryOffset}</Index>\n <Name>${objd.name}</Name>`;
var subindex = 0;
switch (objd.otype) {
case OTYPE.VAR: {
const esiType = esiVariableTypeName(objd);
const bitsize = esiDTbitsize(objd.dtype);
esi += `\n <Entry>\n <Index>#x${index}</Index>\n <SubIndex>#x${subindex.toString(16)}</SubIndex>\n <BitLen>${bitsize}</BitLen>\n <Name>${objd.name}</Name>\n <DataType>${esiType}</DataType>\n </Entry>`;
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 <Entry>\n <Index>#x${index}</Index>\n <SubIndex>#x${subindex.toString(16)}</SubIndex>\n <BitLen>${bitsize}</BitLen>\n <Name>${subitem.name}</Name>\n <DataType>${esiType}</DataType>\n </Entry>`;
// 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 <Entry>\n <Index>#x${index}</Index>\n <SubIndex>#x${subindex.toString(16)}</SubIndex>\n <BitLen>${bitsize}</BitLen>\n <Name>${subitem.name}</Name>\n <DataType>${esiType}</DataType>\n </Entry>`;
esi += pdoBooleanPadding(subitem);
++subindex;
});
break;
}
default: {
alert(`Unexpected OTYPE ${objd.otype} for ${index} ${objd.name} in ESI ${PdoName}PDOs`);
break;
}}
esi += `\n </${PdoName}xPdo>\n`;
return esi;
function pdoBooleanPadding(item) {
if (item.dtype == DTYPE.BOOLEAN) {
return `\n <Entry>\n <Index>${0}</Index>\n <SubIndex>${0}</SubIndex>\n <BitLen>${7}</BitLen>\n </Entry>`;
}
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 <PdoMapping>${pdoMappingFlag}</PdoMapping>`;
}
return flags;
}
function getEsiDCsection(dc) {
if (!dc) {
return '';
}
var dcSection = ' <Dc>';
dc.forEach(opMode => {
dcSection += `\n <OpMode>\n <Name>${opMode.Name}</Name>\n <Desc>${opMode.Description}</Desc>\n <AssignActivate>${opMode.AssignActivate}</AssignActivate>`;
if (opMode.Sync0cycleTime && opMode.Sync0cycleTime != 0) {
dcSection += `\n <CycleTimeSync0>${opMode.Sync0cycleTime}</CycleTimeSync0>`;
}
if (opMode.Sync0shiftTime && opMode.Sync0shiftTime != 0) {
dcSection += `\n <ShiftTimeSync0>${opMode.Sync0shiftTime}</ShiftTimeSync0>`;
}
if (opMode.Sync1cycleTime && opMode.Sync1cycleTime != 0) {
dcSection += `\n <CycleTimeSync1>${opMode.Sync1cycleTime}</CycleTimeSync1>`;
}
if (opMode.Sync1shiftTime && opMode.Sync1shiftTime != 0) {
dcSection += `\n <ShiftTimeSync1>${opMode.Sync1shiftTime}</ShiftTimeSync1>`;
}
dcSection += `\n </OpMode>`;
});
dcSection += `\n </Dc>\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;
}
}
}

View File

@@ -0,0 +1,233 @@
/**
* SOES EEPROM generator
* objectlist.c 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'
// ####################### Objectlist.c generating ####################### //
function get_objdBitsize(element) {
let bitsize = dtype_bitsize[element.dtype];
if (element.dtype == DTYPE.VISIBLE_STRING) {
bitsize = bitsize * element.data.length;
}
return bitsize;
}
/** Takes object dictionary item from sdo, rxpdo or txpdo section
* Adds objd.data that is displayed in objectlist.c
* and links to OD variable declared on OD struct in utypes.h
*/
function objectlist_link_utypes(objd) {
switch (objd.otype) {
case OTYPE.VAR: {
objd.data = objectlist_VAR_data(objd);
break;
}
case OTYPE.ARRAY: {
var subindex = 1;
objd.items.slice(subindex).forEach(subitem => {
subitem.data = objectlist_ARRAY_data(objd, subindex);
++subindex;
});
break;
}
case OTYPE.RECORD: {
var subindex = 1;
objd.items.slice(subindex).forEach(subitem => {
subitem.data = objectlist_RECORD_data(objd, subitem);
++subindex;
});
break;
}
}
function objectlist_VAR_data(objd) {
return `&Obj.${variableName(objd.name)}`;
}
function objectlist_ARRAY_data(objd, subindex) {
return `&Obj.${variableName(objd.name)}[${subindex - 1}]`;
}
function objectlist_RECORD_data(objd, subitem) {
return `&Obj.${variableName(objd.name)}.${variableName(subitem.name)}`;
}
}
function objectlist_generator(form, od, indexes)
{
var objectlist = '#include "esc_coe.h"\n#include "utypes.h"\n#include <stddef.h>\n\n';
//Variable names
indexes.forEach(index => {
const objd = od[index];
objectlist += objectlist_variableName(index, objd);
});
objectlist += '\n';
//SDO objects declaration
indexes.forEach(index => {
const objd = od[index];
objectlist += objectlist_SdoObjectDeclaration(index, objd);
})
objectlist += '\n\nconst _objectlist SDOobjects[] =\n{';
//SDO object dictionary declaration
indexes.forEach(index => {
const objd = od[index];
objectlist += objectlist_DictionaryDeclaration(index, objd);
})
objectlist += '\n {0xffff, 0xff, 0xff, 0xff, NULL, NULL}\n};\n';
return objectlist;
function objectlist_variableName(index, objd) {
var objectlist = `\nstatic const char acName${index}[] = "${objd.name}";`;
switch (objd.otype) {
case OTYPE.VAR:
break;
case OTYPE.ARRAY:
case OTYPE.RECORD:
for (let subindex = 0; subindex < objd.items.length; subindex++) {
const item = objd.items[subindex];
objectlist += `\nstatic const char acName${index}_${subindex_padded(subindex)}[] = "${item.name}";`;
}
break;
default:
alert("Unexpected object type in object dictionary: ", objd)
break;
};
return objectlist;
}
function objectlist_SdoObjectDeclaration(index, objd) {
var objectlist = `\nconst _objd SDO${index}[] =\n{`;
switch (objd.otype) {
case OTYPE.VAR: {
const value = objectlist_getItemValue(objd, objd.dtype);
objectlist += `\n {0x0, DTYPE_${objd.dtype}, ${get_objdBitsize(objd)}, ${objectlist_objdFlags(objd)}, acName${index}, ${value}, ${objeclist_objdData(objd)}},`;
break;
}
case OTYPE.ARRAY: {
objectlist += `\n {0x00, DTYPE_${DTYPE.UNSIGNED8}, ${8}, ATYPE_RO, acName${index}_00, ${objd.items.length - 1}, NULL},`; // max subindex
const bitsize = dtype_bitsize[objd.dtype]; /* TODO what if it is array of strings? */
let subindex = 1;
objd.items.slice(subindex).forEach(subitem => {
var subi = subindex_padded(subindex);
const value = objectlist_getItemValue(subitem, objd.dtype);
objectlist += `\n {0x${subi}, DTYPE_${objd.dtype}, ${bitsize}, ${objectlist_objdFlags(objd)}, acName${index}_${subi}, ${value}, ${subitem.data || 'NULL'}},`;
subindex++;
});
break;
}
case OTYPE.RECORD: {
objectlist += `\n {0x00, DTYPE_${DTYPE.UNSIGNED8}, ${8}, ATYPE_RO, acName${index}_00, ${objd.items.length - 1}, NULL},`; // max subindex
let subindex = 1;
objd.items.slice(subindex).forEach(subitem => {
var subi = subindex_padded(subindex);
const bitsize = dtype_bitsize[subitem.dtype];
const value = objectlist_getItemValue(subitem, subitem.dtype);
const atypeflag = objectlist_objdFlags(subitem);
objectlist += `\n {0x${subi}, DTYPE_${subitem.dtype}, ${bitsize}, ${atypeflag}, acName${index}_${subi}, ${value}, ${subitem.data || 'NULL'}},`;
subindex++;
});
break;
}
default:
alert("Unexpected object type om object dictionary");
break;
};
objectlist += '\n};';
return objectlist;
}
function objectlist_DictionaryDeclaration(index, objd) {
var objectlist = ``;
switch (objd.otype) {
case OTYPE.VAR:
case OTYPE.ARRAY:
case OTYPE.RECORD:
let maxsubindex = 0;
if (objd.items) {
maxsubindex = objd.items.length - 1;
}
objectlist += `\n {0x${index}, OTYPE_${objd.otype}, ${maxsubindex}, ${objd.pad1 || 0}, acName${index}, SDO${index}},`;
break;
default:
alert("Unexpected object type om object dictionary")
break;
};
return objectlist;
}
function float32ToHex(float32) {
// made by: Jozo132 (https://github.com/Jozo132)
const getHex = i => ('00' + i.toString(16)).slice(-2);
var view = new DataView(new ArrayBuffer(4))
view.setFloat32(0, float32);
return Array.apply(null, { length: 4 }).map((_, i) => getHex(view.getUint8(i))).join('');
}
function objectlist_getItemValue(item, dtype) {
let value = '0';
if (item.value) {
value = `${item.value}`;
if (dtype == DTYPE.REAL32) {
return `0x${float32ToHex(value)}`;
}
}
return value;
}
function subindex_padded(subindex) {
// pad with 0 if single digit
if (subindex > 9) {
return `${subindex}`;
}
return `0${subindex}`;
}
/** Gets flags for objectlist item:
*
* ATYPE: access type (RO/RW/WO/RWpre)
*
* PDO mappings */
function objectlist_objdFlags(element) {
let flags = "ATYPE_RO"; // RO by default
if (element.access) {
flags = `ATYPE_${element.access}`;
}
if (element.pdo_mappings) {
element.pdo_mappings .forEach(mapping => {
flags = `${flags} | ATYPE_${mapping.toUpperCase()}`;
});
}
return flags;
}
function objeclist_objdData(element) {
let el_data = 'NULL';
if (element.data) {
el_data = element.data;
if (element.dtype == DTYPE.VISIBLE_STRING) {
el_data = `"${element.data}"`;
}
}
/* TODO el_data is assigned also for PDO mapped variables */
return el_data;
}
}

View File

@@ -0,0 +1,88 @@
/**
* SOES EEPROM generator
* utypes.h 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'
// ####################### utypes.h generation ####################### //
function utypes_generator(form, od, indexes) {
var utypes = '#ifndef __UTYPES_H__\n#define __UTYPES_H__\n\n#include "cc.h"\n\n/* Object dictionary storage */\n\ntypedef struct\n{\n /* Identity */\n'
utypes += '\n uint32_t serial;\n';
var utypesInputs = '\n /* Inputs */\n';
var utypesOutputs = '\n /* Outputs */\n';
var hasInputs = isPdoWithVariables(od, indexes, txpdo);
var hasOutputs = isPdoWithVariables(od, indexes, rxpdo);
indexes.forEach(index => {
const objd = od[index];
if (objd.pdo_mappings) {
if(objd.pdo_mappings.length > 1) { alert(`${index} ${objd.name} Generating utypes.h for objects with multiple PDO mappings is not yet supported`); }
const line = getUtypesDeclaration(objd);
if (objd.pdo_mappings[0] == txpdo) {
utypesInputs += line;
} else {
utypesOutputs += line;
}
}
});
if (hasInputs) { utypes += utypesInputs + '\n'; }
if (hasOutputs) { utypes += utypesOutputs + '\n'; }
var utypesOutputs = '\n /* Parameters */\n';
var anyParameters = false;
indexes.forEach(index => {
const objd = od[index];
if (objd.isSDOitem) {
utypesOutputs += getUtypesDeclaration(objd);
anyParameters = true;
}
});
if (anyParameters) {
utypes += utypesOutputs;
}
utypes += '\n} _Objects;\n\nextern _Objects Obj;\n\n#endif /* __UTYPES_H__ */\n';
return utypes;
function getUtypesDeclaration(objd) {
const varName = variableName(objd.name);
switch (objd.otype) {
case OTYPE.VAR: {
const ctype = ESI_DT[objd.dtype].ctype;
return `\n ${ctype} ${varName};`
}
case OTYPE.ARRAY: {
const ctype = ESI_DT[objd.dtype].ctype;
return `\n ${ctype} ${varName}[${objd.items.length - 1}];`
}
case OTYPE.RECORD: {
var section = `\n struct\n {`;
/* TODO test */
objd.items.slice(1).forEach(subitem => {
const subitemCType = ESI_DT[subitem.dtype].ctype;
const subitemName = variableName(subitem.name);
section += `\n ${subitemCType} ${subitemName};`
});
section += `\n } ${varName};`
return section;
}
default: {
alert(`Cannot generate utypes.h for object ${objd?.name} with has unexpected OTYPE ${objd?.otype}`);
return '';
}
}
}
}

View 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;
}

View File

@@ -0,0 +1,627 @@
/**
* SOES EEPROM generator
* UI behavior 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'
// ####################### UI changes handlers ####################### //
function getForm() {
return document.getElementById("SlaveForm");
}
function getOutputForm() {
return document.getElementById("outCodeForm");
}
function onFormChanged() {
const form = getForm();
saveLocalBackup(form);
processForm(form);
}
/** Shortcuts:
* Ctrl + S to save project
* Ctrl + O to load save file
* Shortcuts start to work after user clicked on page
*/
document.onkeydown = function(e) {
const S_keyCode = 83;
const O_keyCode = 79;
if (e.ctrlKey){
if (e.keyCode === S_keyCode) {
event.preventDefault();
onSaveClick();
return false;
}
else if (e.keyCode == O_keyCode) {
event.preventDefault();
onRestoreClick();
return false;
}
}
};
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == odModal) {
odModalClose();
}
}
window.onload = (event) => {
odModalSetup();
syncModalSetup();
const form = getForm();
setFormValues(form, getFormDefaultValues());
tryRestoreLocalBackup(form);
// for convinience during tool development, trigger codegen on page refresh
processForm(form);
const _isComputerFast = automaticCodegen;
if (_isComputerFast) {
// code is regenerated on every form change.
// no need to remember to generate before copying or downloading
// app is noticeably slower
processForm(form); // make sure displayed code is up to date at startup, e.g redo, if it came from backup
document.getElementById('GenerateFilesButton').style.display = 'none'; // 'generate' button no longer needed
form.addEventListener('change', function() {
onFormChanged();
});
}
setupDarkMode();
}
// ####################### dark mode logic ####################### //
function setupDarkMode() {
if (!localStorage.darkMode) {
localStorage.darkMode = 'dark'; // dark mode by default
}
document.documentElement.setAttribute("data-theme", localStorage.darkMode);
}
function toggleDarkMode() {
var newMode = (localStorage.darkMode == 'dark') ? "light" : "dark"
localStorage.darkMode = newMode;
document.documentElement.setAttribute("data-theme", localStorage.darkMode);
}
// ####################### code generation UI logic ####################### //
/** Code generation method, triggered by UI */
function processForm(form)
{
const od = buildObjectDictionary(form);
const indexes = getUsedIndexes(od);
var outputCtl = getOutputForm();
outputCtl.objectlist.value = objectlist_generator(form, od, indexes);
outputCtl.ecat_options.value = ecat_options_generator(form, od, indexes);
outputCtl.utypes.value = utypes_generator(form, od, indexes);
outputCtl.HEX.hexData = hex_generator(form);
outputCtl.HEX.value = toIntelHex(outputCtl.HEX.hexData);
outputCtl.ESI.value = esi_generator(form, od, indexes, _dc);
saveLocalBackup(form);
return outputCtl;
}
// ####################### Button handlers ####################### //
function getProjectName(form) {
return variableName(form.TextDeviceName.value);
}
function onGenerateDownloadClick()
{
const form = getForm();
var result = processForm(form);
downloadGeneratedFilesZipped(form, result);
function downloadGeneratedFilesZipped(form, result) {
var zip = new JSZip();
const projectName = getProjectName(form);
zip.file(`${projectName}.xml`, result.ESI.value);
zip.file('eeprom.hex', result.HEX.value);
zip.file('eeprom.bin', result.HEX.hexData);
zip.file('ecat_options.h', result.ecat_options.value);
zip.file('objectlist.c', result.objectlist.value);
zip.file('utypes.h', result.utypes.value);
zip.file('esi.json', prepareBackupFileContent(form));
zip.generateAsync({type:"blob"}).then(function (blob) { // generate the zip file
downloadFile(blob, "esi.zip", "application/zip"); // trigger the download
}, function (err) {
console.log(err);
});
}
function downloadGeneratedFiles(form, result) {
const projectName = getProjectName(form);
downloadFile(result.ESI.value, `${projectName}.xml`, 'text/html');
downloadFile(result.HEX.value, 'eeprom.hex', 'application/octet-stream');
downloadFile(result.ecat_options.value, 'ecat_options.h', 'text/plain');
downloadFile(result.objectlist.value, 'objectlist.c', 'text/plain');
downloadFile(result.utypes.value, 'utypes.h', 'text/plain');
downloadBackupFile(form);
}
}
function onGenerateClick() {
processForm(getForm());
}
function onSaveClick() {
const form = getForm();
downloadBackupFile(form);
saveLocalBackup(form);
}
function onRestoreClick() {
// trigger file input dialog window
document.getElementById('restoreFileInput').click();
}
function onRestoreComplete(fileContent) {
const form = getForm();
restoreBackup(fileContent, form);
processForm(form);
}
function onResetClick() {
if (confirm("Are you sure you want to reset project to default values?")){
resetLocalBackup();
location.reload(true);
}
}
function onDownloadEsiXmlClick() {
const form = getForm();
const projectName = getProjectName(form);
downloadFile(getOutputForm().ESI.value, `${projectName}.xml`, 'text/html');
}
function onDownloadBinClick() {
const record = getOutputForm().HEX.hexData;
if (!record) { alert("Generate code before you can download it"); return; }
downloadFile(record, 'eeprom.bin', 'application/octet-stream');
}
// ####################### Handle modal dialog ####################### //
var odModal = {};
function odModalSetup() {
// Get the modal
odModal = document.getElementById("editObjectModal");
if (odModal) {
odModal.form = document.getElementById('EditObjectForm');
}
else {
alert("Element required to edit Object Dictionary not found!");
}
}
// When the user clicks the button, open the modal
function odModalOpen() {
odModal.style.display = "block";
}
function odModalClose() {
odModal.style.display = "none";
}
/** update control values on OD modal */
function odModalUpdate(index, objd) {
odModal.form.Index.value = `0x${index}`;
odModal.form.ObjectName.value = objd.name;
odModal.form.DTYPE.value = objd.dtype || DTYPE.UNSIGNED8;
odModal.form.Access.value = objd.access || 'RO';
odModal.objd = objd;
}
function odModalHideControls() {
document.getElementById('dialogRowIndex').style.display = 'none';
document.getElementById('dialogRowDtype').style.display = 'none';
document.getElementById('dialogRowValue').style.display = 'none';
document.getElementById('dialogRowAccess').style.display = 'none';
}
// ####################### Modal dialogs for OD edition ####################### //
function editExistingOD_ObjectDialog(odSectionName, index, otype) {
const od = getObjDictSection(odSectionName);
var objd = od[index];
odModal.index_initial_value = index;
checkObjectType(otype, objd);
odModalUpdate(index, objd);
}
function addNewOD_ObjectDialog(odSectionName, otype) {
var objd = getNewObjd(odSectionName, otype);
var index = getFirstFreeIndex(odSectionName);
delete odModal.index_initial_value; // add new object, not replace edited one
odModalUpdate(index, objd);
}
function odModalOpenForObject(otype) {
odModalHideControls();
document.getElementById('dialogRowIndex').style.display = '';
document.getElementById('dialogRowAccess').style.display = '';
switch (otype) {
case OTYPE.VAR: {
document.getElementById('dialogRowDtype').style.display = '';
document.getElementById('dialogRowValue').style.display = '';
break;
}
case OTYPE.ARRAY: {
document.getElementById('dialogRowDtype').style.display = "";
break;
}
case OTYPE.RECORD: {
break;
}
default: {
alert(`Unknown object type ${otype}, cannot open modal for it!`);
return;
}
}
odModalOpen();
document.getElementById('modalInputIndex').focus();
}
function odModalSetTitle(message) {
document.getElementById('editObjectTitle').innerHTML = `<strong>${message}</strong>`;
}
// ####################### Edit Object Dictionary UI logic ####################### //
function editVAR_Click(odSectionName, indexValue = null) {
const otype = OTYPE.VAR;
const index = indexToString(indexValue);
var actionName = "Edit";
odModal.odSectionName = odSectionName;
if (objectExists(odSectionName, index)) {
editExistingOD_ObjectDialog(odSectionName, index, otype);
odModal.form.DTYPE.value = odModal.objd.dtype;
} else {
addNewOD_ObjectDialog(odSectionName, otype);
actionName = "Add"
}
odModalSetTitle(`${actionName} ${odSectionName.toUpperCase()} variable`);
odModalOpenForObject(otype);
}
function editARRAY_Click(odSectionName, indexValue = null) {
const otype = OTYPE.ARRAY;
const index = indexToString(indexValue);
var actionName = "Edit";
odModal.odSectionName = odSectionName;
odModal.form.Access
if (objectExists(odSectionName, index)) {
editExistingOD_ObjectDialog(odSectionName, index, otype);
odModal.form.DTYPE.value = odModal.objd.dtype;
} else {
addNewOD_ObjectDialog(odSectionName, otype);
actionName = "Add"
}
odModalSetTitle(`${actionName} ${odSectionName.toUpperCase()} array`);
odModalOpenForObject(otype);
}
function editRECORD_Click(odSectionName, indexValue = null) {
const otype = OTYPE.RECORD;
const index = indexToString(indexValue);
var actionName = "Edit";
odModal.odSectionName = odSectionName;
if (objectExists(odSectionName, index)) {
editExistingOD_ObjectDialog(odSectionName, index, otype);
} else {
addNewOD_ObjectDialog(odSectionName, otype);
actionName = "Add"
}
odModalSetTitle(`${actionName} ${odSectionName.toUpperCase()} record`);
odModalOpenForObject(otype);
}
function onEditObjectSubmit(modalform) {
if (odModal.subitem) {
onEditSubitemSubmit(odModal.subitem);
delete odModal.subitem;
return;
}
const objd = odModal.objd;
const objectType = objd.otype;
const index = indexToString(modalform.Index.value);
objd.name = modalform.ObjectName.value;
switch (objectType) {
case OTYPE.VAR:
objd.dtype = modalform.DTYPE.value;
if (objd.dtype == DTYPE.VISIBLE_STRING) {
objd.data = ''
} else {
objd.value = modalform.InitalValue.value;
}
break;
case OTYPE.ARRAY:
objd.dtype = modalform.DTYPE.value;
break;
case OTYPE.RECORD:
break;
default:
alert(`Unexpected type ${objectType} on object ${modalform.ObjectName} being edited!`);
break;
}
const odSection = getObjDictSection(odModal.odSectionName);
if (odModal.index_initial_value) {
removeObject(odSection, odModal.index_initial_value); // detach from OD, to avoid duplicate if index changed
}
addObject(odSection, objd, index); // attach updated object
odModalClose();
reloadOD_Section(odModal.odSectionName);
delete odModal.odSectionName;
odModal.objd = {};
onFormChanged();
}
function onRemoveClick(odSectionName, indexValue, subindex = null) {
const index = indexToString(indexValue);
const odSection = getObjDictSection(odSectionName);
const objd = odSection[index];
if(!objd) { alert(`${odSectionName.toUpperCase()} object ${index} does not exist!`); return; }
if(subindex) {
if(!objd.items) { alert(`Object 0x${index} "${objd.name}" does not have any items!`); return; }
if(objd.items.length < subindex) { alert(`Object 0x${index} "${objd.name}" does not have enough items!`); return; }
if(objd.items.length < 3) { // only max subindex and one more subitem
alert(`Object 0x${index} "${objd.name}" has only 1 subitem, it should not be empty. Remove entire object instead.`); return; }
}
if (confirm(getConfirmMessage(objd, index, subindex))) {
if (subindex) {
const subitemsToRemove = 1;
objd.items.splice(subindex, subitemsToRemove);
} else {
removeObject(odSection, index);
}
reloadOD_Section(odSectionName);
onFormChanged();
}
function getConfirmMessage(objd, index, subindex) {
if (subindex) {
return `Are you sure you want to remove subitem ${subindex} "${objd.items[subindex].name }" from object 0x${index} "${objd.name}"?`
}
return `Are you sure you want to remove object 0x${index} "${objd.name}"?`
}
}
function addSubitemClick(odSectionName, indexValue) {
const index = indexToString(indexValue);
const odSection = getObjDictSection(odSectionName);
const objd = odSection[index];
// we expect objd to have items array with at least [{ name: 'Max SubIndex' }]
if(!objd.items || !objd.items.length ) { alert(`Object ${index} "${objd.name}" has no subitems!`); return; }
switch(objd.otype) {
case OTYPE.ARRAY: {
addArraySubitem(objd);
break;
}
case OTYPE.RECORD: {
addRecordSubitem(objd);
break;
}
default: {
alert(`Object ${index} "${objd.name}" has OTYPE ${objd.otype} so cannot add subitems`);
}
}
const subindex = objd.items.length - 1; // subitem is added to end of items list
editSubitemClick(odSectionName, indexValue, subindex, "Add");
}
function editSubitemClick(odSectionName, indexValue, subindex, actionName = "Edit") {
const index = indexToString(indexValue);
const odSection = getObjDictSection(odSectionName);
const objd = odSection[index];
if(!objd.items || objd.items.length <= subindex ) { alert(`Object ${index} "${objd.name}" does not have ${subindex} subitems!`); return; }
odModalSetTitle(`${actionName} ${odSectionName.toUpperCase()} object 0x${index} "${objd.name}" subitem 0x${indexToString(subindex)}`);
const subitem = objd.items[subindex];
odModalHideControls();
document.getElementById('dialogRowValue').style.display = "";
odModal.form.InitalValue.value = subitem.value ?? 0;
if (objd.otype == OTYPE.RECORD) {
document.getElementById('dialogRowDtype').style.display = "";
odModal.form.DTYPE.value = subitem.dtype;
document.getElementById('dialogRowAccess').style.display = ''; // access for record subitems can differ
odModal.form.Access.value = subitem.access || 'RO';
}
odModal.form.ObjectName.value = subitem.name;
odModal.subitem = { odSectionName: odSectionName, index: index, subindex: subindex };
odModalOpen();
document.getElementById('modalInputObjectName').focus();
}
function onEditSubitemSubmit(modalSubitem) {
const odSection = getObjDictSection(modalSubitem.odSectionName);
const objd = odSection[modalSubitem.index];
const subitem = objd.items[modalSubitem.subindex];
subitem.name = odModal.form.ObjectName.value;
subitem.value = odModal.form.InitalValue.value;
if (objd.otype == OTYPE.RECORD) {
subitem.dtype = odModal.form.DTYPE.value;
subitem.access = odModal.form.Access.value;
}
odModalClose();
onFormChanged();
reloadOD_Section(modalSubitem.odSectionName);
}
// ####################### Display Object Dictionary state ####################### //
function reloadOD_Sections() {
reloadOD_Section(sdo);
reloadOD_Section(txpdo);
reloadOD_Section(rxpdo);
}
function reloadOD_Section(odSectionName) {
const odSection = getObjDictSection(odSectionName);
var indexes = getUsedIndexes(odSection);
var section = '';
indexes.forEach(index => {
const objd = odSection[index];
section += `<div class="odItem"><span class="odItemContent"><strong>0x${index}</strong> &nbsp; &nbsp; "${objd.name}" ${objd.otype} ${objd.dtype ?? ''}</span><span>`;
if (objd.otype == OTYPE.ARRAY || objd.otype == OTYPE.RECORD) {
section += `<button onClick='addSubitemClick(${odSectionName}, 0x${index})'>&nbsp; Add subitem &nbsp;</button>`;
}
section += `<button onClick='onRemoveClick(${odSectionName}, 0x${index})'>&nbsp; ❌ Remove &nbsp;</button>`;
section += `<button onClick='edit${objd.otype}_Click(${odSectionName}, 0x${index})'>&nbsp; 🛠️ &nbsp; Edit &nbsp;</button>`;
section += `</span></div>`;
if (objd.items) {
var subindex = 1; // skip Max Subindex
objd.items.slice(subindex).forEach(subitem => {
var subindexHex = subindex < 16 ? `0${subindex.toString(16)}` : subindex.toString(16);
section += `<div class="odSubitem"><span class="odSubitemContent"><strong>:0x${subindexHex}</strong>&nbsp;&nbsp; "${subitem.name}" ${subitem.dtype ?? ''}</span>`;
section += `<span><button onClick='onRemoveClick(${odSectionName}, 0x${index}, ${subindex})'>&nbsp; ❌ Remove &nbsp;</button>`;
section += `<button onClick='editSubitemClick(${odSectionName}, 0x${index}, ${subindex})'>&nbsp; 🛠️ &nbsp; Edit &nbsp;</button>`;
section += `</span></div>`;
++subindex;
});
}
});
const odSectionElement = document.getElementById(`tr_${odSectionName}`);
if (odSectionElement) {
odSectionElement.innerHTML = section;
}
}
// ####################### Synchronization settings UI ####################### //
var syncModal = {};
var _dc = []
function syncModalSetup() {
// Get the modal
syncModal = document.getElementById("syncModal");
if (syncModal) {
syncModal.form = document.getElementById('syncModalForm');
}
else {
alert("Element required to edit Object Dictionary not found!");
}
}
function syncModalClose() {
syncModal.style.display = "none";
delete syncModal.edited;
reloadSyncModes();
}
function syncModalOpen() {
syncModal.style.display = "block";
}
function syncModeEdit(sync) {
syncModal.edited = sync;
syncModal.form.Name.value = sync.Name;
syncModal.form.Description.value = sync.Description;
syncModal.form.AssignActivate.value = sync.AssignActivate;
syncModal.form.Sync0cycleTime.value = sync.Sync0cycleTime;
syncModal.form.Sync0shiftTime.value = sync.Sync0shiftTime;
syncModal.form.Sync1cycleTime.value = sync.Sync1cycleTime;
syncModal.form.Sync1shiftTime.value = sync.Sync1shiftTime;
syncModalOpen();
}
function onSyncSubmit(syncForm) {
syncModal.edited.Name = syncForm.Name.value;
syncModal.edited.Description = syncForm.Description.value;
syncModal.edited.AssignActivate = syncForm.AssignActivate.value;
syncModal.edited.Sync0cycleTime = syncForm.Sync0cycleTime.value;
syncModal.edited.Sync0shiftTime = syncForm.Sync0shiftTime.value;
syncModal.edited.Sync1cycleTime = syncForm.Sync1cycleTime.value;
syncModal.edited.Sync1shiftTime = syncForm.Sync1shiftTime.value;
syncModalClose();
onFormChanged();
}
// ####################### Synchronization settings button handlers ####################### //
function addSyncClick() {
const newSyncMode = {
Name: "DcOff",
Description: "DC unused",
AssignActivate: "#x000",
Sync0cycleTime: 0,
Sync0shiftTime: 0,
Sync1cycleTime: 0,
Sync1shiftTime: 0,
}
_dc.push(newSyncMode);
syncModeEdit(newSyncMode);
}
function onEditSyncClick(i) {
const syncMode = _dc[i];
syncModeEdit(syncMode);
}
function onRemoveSyncClick(i) {
_dc.splice(i, 1);
reloadSyncModes();
onFormChanged();
}
// ####################### Synchronization settings UI ####################### //
function reloadSyncModes() {
var section = '';
var i = 0;
_dc.forEach(sync => {
section += `<div class="odItem"><span class="odItemContent"><strong>${sync.Name}</strong> &nbsp; &nbsp; ${sync.Description} &nbsp; [${sync.AssignActivate}]</span><span>`;
section += `<button onClick='onRemoveSyncClick(${i})'>&nbsp; ❌ Remove &nbsp;</button>`;
section += `<button onClick='onEditSyncClick(${i})'>&nbsp; 🛠️ &nbsp; Edit &nbsp;</button>`;
section += `</span></div>`;
++i;
});
const sectionElement = document.getElementById(`dcSyncModes`);
if (sectionElement) {
sectionElement.innerHTML = section;
}
}

View File

@@ -0,0 +1,195 @@
:root {
--background-color: #fff;
--text-color: #000;
--input-color: #ffffff;
--display-dark: none;
--display-light: block;
}
/* dark mode */
[data-theme="dark"] {
/* body, .modal-content { */
--background-color: #2D2D2D; /* #4D4D4D; */
--text-color: white;
--input-color: #3D3D3D;
--display-dark: block;
--display-light: none;
}
tr td:nth-child(3) {
text-align: center;
}
tr td:nth-child(4) {
text-align: center;
}
tr td:nth-child(5) {
text-align: center;
}
tr td:nth-child(6) {
text-align: center;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 18%; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
background-color: var(--background-color);
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 450px;
}
#editObjectTitle {
margin-bottom: 25px;
}
/* The Close Button */
.close {
color: #aaaaaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
/* Dropdown Button */
.dropbtn {
padding: 6px 16px;
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: var(--background-color);
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
/* width: 100; */
text-decoration: none;
margin: 10px 4px;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd;}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {display: block;}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: var(--background-color);}
/* main content layout */
body {
font-family: Arial, Helvetica, sans-serif;
font-size: large;
}
div.main-wrapper {
margin: auto;
max-width: 960px;
}
h1, h2, h3 {
text-align: center;
}
table, textarea {
width: 100%;
}
button {
margin-left: 5px;
margin-right: 5px;
}
div.settings-menu {
display: flex;
justify-content: space-between;
/* align-items: right; */
/* background-color: red; */
}
.odSectionHeading, .odItem, .odSubitem {
display: flex;
align-items: center;
justify-content: space-between;
}
div.odItem, div.odSubitem {
padding-top: 4px;
padding-bottom: 4px;
width: 100%;
border-top: 1px dotted;
border-bottom: 1px dotted;
}
h3.odSectionHeading {
text-align: left;
}
span.odSectionTitle {
font-weight: 800;
margin-top: 10px;
margin-right: -5px;
}
span.odSubitemContent {
margin-left: 15px;
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
input, textarea, select, button {
background-color: var(--input-color);
color: var(--text-color);
}
.display-dark {
display: var(--display-dark);
}
.display-light {
display: var(--display-light);
}
a {
color: var(--text-color);
font-weight: 600;
}
button {
font-size: 16px;
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SOES EEPROM Generator spec runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-3.8.0/jasmine_favicon.png">
<link rel="stylesheet" href="lib/jasmine-3.8.0/jasmine.css">
<script src="lib/jasmine-3.8.0/jasmine.js"></script>
<script src="lib/jasmine-3.8.0/jasmine-html.js"></script>
<script src="lib/jasmine-3.8.0/boot.js"></script>
<!-- include source files here... -->
<script src="src/constants.js"></script>
<script src="src/od.js"></script>
<script src="src/file_io.js"></script>
<script src="src/backup.js"></script>
<script src="src/generators/EEPROM.js"></script>
<script src="src/generators/esi_xml.js"></script>
<script src="src/generators/ecat_options.js"></script>
<script src="src/generators/objectlist.js"></script>
<script src="src/generators/utypes.js"></script>
<!-- include spec files here... -->
<script src="spec/helpers/formMockHelper.js"></script>
<!-- include spec files here... -->
<script src="spec/backupSpec.js"></script>
<script src="spec/generatorsSpec.js"></script>
</head>
<body>
</body>
</html>