| | <!DOCTYPE html> |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | <html> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <title>02 — Utility Functions | Siemens Configurator</title> |
| | <style> |
| | body { font-family: Arial, sans-serif; background: #f5f5f5; color: #222; margin: 20px; } |
| | h1 { color: #003366; border-bottom: 3px solid #003366; padding-bottom: 6px; } |
| | h2 { color: #005599; margin-top: 30px; } |
| | h3 { color: #007733; } |
| | pre.code { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 6px; |
| | font-family: Consolas, monospace; font-size: 13px; overflow-x: auto; |
| | border-left: 4px solid #569cd6; white-space: pre-wrap; } |
| | .comment { color: #6a9955; } |
| | .keyword { color: #569cd6; } |
| | .string { color: #ce9178; } |
| | .number { color: #b5cea8; } |
| | .note { background: #fff8dc; border: 2px solid #f0c040; border-radius: 6px; padding: 12px; margin: 10px 0; } |
| | .explain { background: #e8f5e9; border: 2px solid #4caf50; border-radius: 6px; padding: 12px; margin: 10px 0; } |
| | .warn { background: #fff3e0; border: 2px solid #ff9800; border-radius: 6px; padding: 12px; margin: 10px 0; } |
| | nav { background: #003366; padding: 10px; border-radius: 6px; } |
| | nav a { color: #fff; text-decoration: none; margin-right: 18px; font-size: 14px; } |
| | nav a:hover { text-decoration: underline; } |
| | .sig { background: #003366; color: #fff; padding: 4px 10px; border-radius: 4px; font-family: monospace; display: inline-block; margin-bottom: 8px; } |
| | </style> |
| | </head> |
| | <body> |
| |
|
| | <nav> |
| | <a href="index.html">📋 README</a> |
| | <a href="01_config_variables.html">① Config</a> |
| | <a href="02_utility_functions.html">② Utilities</a> |
| | <a href="03_drive_configuration.html">③ Drive Config</a> |
| | <a href="04_ui_html_builder.html">④ HTML Builder</a> |
| | <a href="05_event_handlers.html">⑤ Event Handlers</a> |
| | <a href="06_machine_data.html">⑥ Machine Data</a> |
| | </nav> |
| |
|
| | <h1>② Utility Functions</h1> |
| | <p> |
| | These are the <strong>shared helper functions</strong> used throughout the whole script. |
| | They are called dozens (sometimes hundreds) of times during the configuration process. |
| | You don't need to change these unless there is a bug. |
| | </p> |
| |
|
| | |
| | <h2>CreateLogFile</h2> |
| | <div class="sig">Sub CreateLogFile</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Creates a new script called <code>"LogFile"</code> inside the Starter project's Scripts section. |
| | If the log file already exists, it reuses it. Clears any old content. |
| | This is the start of the audit trail — every parameter value changed later is recorded here. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Sub</span> CreateLogFile |
| | <span class="keyword">Dim</span> ConfigLogFile <span class="comment">' This will hold a reference to the LogFile script object in Starter</span> |
| | <span class="keyword">Dim</span> ConfigLogFileText <span class="comment">' (Reserved, not actually used in the body)</span> |
| |
|
| | <span class="keyword">If</span> ConfigOption02_Logfile <span class="keyword">Then</span> <span class="comment">' Only create the log if logging is enabled in config (see file 01)</span> |
| | On Error Resume Next <span class="comment">' Don't crash if the script already exists — just handle it gracefully</span> |
| |
|
| | Set ConfigLogFile = PROJ.Scripts.Item(ConfigLogFileName) <span class="comment">' Try to get the existing "LogFile" script</span> |
| |
|
| | <span class="keyword">If</span> (Err.Number<>0) <span class="keyword">Then</span> <span class="comment">' If it didn't exist (error occurred)...</span> |
| | APP.PrintToLog <span class="string">"Log file Doesn't exist"</span> <span class="comment">' ...log this fact to Starter's internal log</span> |
| | PROJ.Scripts.Add(ConfigLogFileName) <span class="comment">' ...create a brand-new script named "LogFile"</span> |
| | APP.PrintToLog <span class="string">"Log file created"</span> |
| | Set ConfigLogFile = PROJ.Scripts.Item(ConfigLogFileName) <span class="comment">' ...and get a reference to it</span> |
| | <span class="keyword">End If</span> |
| |
|
| | <span class="comment">' Write a header line to the log so it's easy to see where one session starts</span> |
| | ConfigLogFile.Code=<span class="string">"' "</span>&<span class="string">"-------------------- LogFile created -------------------"</span>&vbCrLf |
| | <span class="comment">' vbCrLf = a newline character (carriage return + line feed)</span> |
| | <span class="keyword">End If</span> |
| | <span class="keyword">End Sub</span> |
| | </pre> |
| |
|
| | |
| | <h2>PrintToLogFile</h2> |
| | <div class="sig">Sub PrintToLogFile( logText )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Appends one line of text to the "LogFile" script in Starter. |
| | Called by <code>SetParam</code>, <code>SetSym</code>, and <code>PrintHeader</code> |
| | each time a parameter or symbol is changed. |
| | The logged text is prefixed with <code>'</code> so that the LogFile reads as valid VBScript comments. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Sub</span> PrintToLogFile(logText) |
| | <span class="keyword">Dim</span> ConfigLogFile |
| | <span class="keyword">If</span> ConfigOption02_Logfile <span class="keyword">Then</span> <span class="comment">' Only log if logging is enabled</span> |
| | Set ConfigLogFile = PROJ.Scripts.Item(ConfigLogFileName) <span class="comment">' Get the LogFile script</span> |
| | ConfigLogFile.Code = ConfigLogFile.Code & <span class="string">"'"</span> & logText & vbCrLf |
| | <span class="comment">' AppendS the new line: ConfigLogFile.Code is the existing content, |
| | ' plus "'" (makes it a VBScript comment), plus the new text, plus a newline</span> |
| | <span class="keyword">End If</span> |
| | <span class="keyword">End Sub</span> |
| | </pre> |
| |
|
| | |
| | <h2>SetParam</h2> |
| | <div class="sig">Sub SetParam( driveTO, parameter, index, newValue )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | This is the <em>most important function</em> in the entire script. |
| | It writes a value to a Siemens drive parameter and records what changed in the log file.<br><br> |
| | <strong>Arguments:</strong> |
| | <ul> |
| | <li><code>driveTO</code> — the drive Technology Object (e.g., the Unwind drive object)</li> |
| | <li><code>parameter</code> — the Siemens parameter number (e.g., <code>210</code> = supply voltage)</li> |
| | <li><code>index</code> — the array index of that parameter (most parameters have [0], some have [1], [2] etc.)</li> |
| | <li><code>newValue</code> — the value to write (e.g., <code>400</code> for 400V)</li> |
| | </ul> |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Sub</span> SetParam(driveTO, parameter, index, newValue) |
| | oldValue = driveTO.Parameters(parameter, index) <span class="comment">' Read the current value BEFORE changing it</span> |
| | driveTO.Parameters(parameter, index) = newValue <span class="comment">' Write the new value to the drive</span> |
| | finalValue = driveTO.Parameters(parameter, index) <span class="comment">' Read back the value to verify it was accepted</span> |
| | <span class="comment">' NOTE: Sometimes drives reject a write if the value is out of range. |
| | ' finalValue lets us see what the drive actually stored.</span> |
| |
|
| | <span class="comment">' ── Build the log line ──────────────────────────────────────────────────────────</span> |
| | <span class="comment">' Each column is padded to LogfilePadLength (15 chars) for aligned CSV output</span> |
| | DriveObjectName = Padding_Text(driveTo.Name, LogfilePadLength, <span class="string">" "</span>) <span class="comment">' e.g. "Unwind "</span> |
| | ParameterType = Padding_Text(<span class="string">"Parameter"</span>, LogfilePadLength, <span class="string">" "</span>) <span class="comment">' always "Parameter "</span> |
| | Parameter = Padding_Text(parameter, LogfilePadLength, <span class="string">" "</span>) <span class="comment">' e.g. "210 "</span> |
| | index = Padding_Text(index, LogfilePadLength, <span class="string">" "</span>) <span class="comment">' e.g. "0 "</span> |
| | OldValue = Padding_Text(oldValue, LogfilePadLength, <span class="string">" "</span>) |
| | NewValue = Padding_Text(newValue, LogfilePadLength, <span class="string">" "</span>) |
| | FinalValue = Padding_Text(finalValue, LogfilePadLength, <span class="string">" "</span>) |
| |
|
| | <span class="keyword">If</span> ParameterEchoToScreen <span class="keyword">Then</span> <span class="comment">' If debug echo is on, also print to HTML status bar</span> |
| | EchoToScreen(DriveObjectName & <span class="string">","</span> & ParameterType & <span class="string">","</span> & Parameter & <span class="string">","</span> & Index & <span class="string">","</span> & oldValue & <span class="string">","</span> & NewValue & <span class="string">","</span> & FinalValue) |
| | <span class="keyword">End If</span> |
| |
|
| | PrintToLogFile(DriveObjectName & <span class="string">","</span> & ParameterType & <span class="string">","</span> & Parameter & <span class="string">","</span> & Index & <span class="string">","</span> & OldValue & <span class="string">","</span> & NewValue & <span class="string">","</span> & FinalValue) |
| | <span class="comment">' Log line example: "Unwind ,Parameter ,210 ,0 ,0 ,400 ,400 "</span> |
| | <span class="keyword">End Sub</span> |
| | </pre> |
| |
|
| | |
| | <h2>Padding_Text</h2> |
| | <div class="sig">Function Padding_Text( StringToPad, FinalStringLen, PadCharacter )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Takes a string and adds spaces (or any other character) to make it exactly |
| | <code>FinalStringLen</code> characters long. This makes the log file columns line up neatly. |
| | If <code>CompressedCSVLogfile = True</code>, it just returns the original string unchanged. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Function</span> Padding_Text(StringToPad, FinalStringLen, PadCharacter) |
| | PaddingLength = FinalStringLen - Len(StringtoPad) <span class="comment">' How many spaces do we need to add?</span> |
| | PaddedStringOut = StringToPad <span class="comment">' Start with the original string</span> |
| |
|
| | <span class="keyword">For</span> i=1 <span class="keyword">To</span> PaddingLength <span class="comment">' Add spaces one at a time until correct length</span> |
| | PaddedStringOut = PaddedStringOut & PadCharacter |
| | <span class="keyword">Next</span> |
| |
|
| | <span class="keyword">If</span> CompressedCSVLogfile = <span class="keyword">True Then</span> |
| | Padding_Text = StringToPad <span class="comment">' Compressed mode: return unpadded original</span> |
| | <span class="keyword">Else</span> |
| | Padding_Text = PaddedStringOut <span class="comment">' Normal mode: return padded string</span> |
| | <span class="keyword">End If</span> |
| | <span class="keyword">End Function</span> |
| | </pre> |
| |
|
| | |
| | <h2>SetSym</h2> |
| | <div class="sig">Sub SetSym( driveTO, sym, newValue )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Similar to <code>SetParam</code>, but writes to a Siemens drive <em>Symbol</em> |
| | (a named alias for a parameter bit) instead of a raw parameter number. |
| | Symbols look like <code>p2100[0].1</code> — meaning parameter 2100, array index 0, bit 1.<br><br> |
| | The function also parses the symbol string to extract the parameter number and index |
| | so they can be logged in the same format as <code>SetParam</code>. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Sub</span> SetSym(driveTO, sym, newValue) |
| | oldValue = driveTO.Symbols(sym) <span class="comment">' Read current symbol value</span> |
| | driveTO.Symbols(sym) = newValue <span class="comment">' Write new value</span> |
| | finalValue = driveTO.Symbols(sym) <span class="comment">' Read back to verify</span> |
| |
|
| | <span class="comment">' ── Parse the symbol string to extract the parameter name and index ──────────</span> |
| | PeriodPosn = instr(sym, <span class="string">"."</span>) <span class="comment">' Find position of the "." — e.g. "p2100[0].1" → period is at position 9</span> |
| | IndexText = mid(sym, PeriodPosn+1) <span class="comment">' Everything after the period = bit index (e.g. "1")</span> |
| |
|
| | IndexStartPosn = instr(sym, <span class="string">"["</span>) <span class="comment">' Find position of "[" for array index</span> |
| | IndexEndPosn = instr(sym, <span class="string">"]"</span>) <span class="comment">' Find position of "]"</span> |
| |
|
| | <span class="comment">' Extract the array digit(s) — could be single digit (0-9) or double digit (10-99)</span> |
| | <span class="keyword">If</span> IndexStartPosn <> 0 <span class="keyword">And</span> IndexEndPosn <> 0 <span class="keyword">Then</span> |
| | <span class="keyword">If</span> (IndexEndPosn - IndexStartPosn) = 2 <span class="keyword">Then</span> |
| | StringText1 = mid(sym, IndexStartPosn+1, 1) <span class="comment">' Single digit: e.g. "[0]" → "0"</span> |
| | <span class="keyword">Else</span> |
| | StringText1 = mid(sym, IndexStartPosn+1, 2) <span class="comment">' Double digit: e.g. "[10]" → "10"</span> |
| | <span class="keyword">End If</span> |
| | <span class="keyword">End If</span> |
| |
|
| | <span class="comment">' Extract just the parameter number (strip the leading "p" character)</span> |
| | StringText = Left(sym, PeriodPosn-1) <span class="comment">' e.g. "p2100[0]"</span> |
| | SymbolText = mid(StringText, 2) <span class="comment">' Remove leading "p" → "2100[0]"</span> |
| |
|
| | <span class="comment">' ── Build and log the output line ──────────────────────────────────────────────</span> |
| | SymbolType = Padding_Text(<span class="string">"Symbol"</span>, LogfilePadLength, <span class="string">" "</span>) |
| | <span class="comment">' ... (pad all values, then call PrintToLogFile — same pattern as SetParam)</span> |
| | <span class="keyword">End Sub</span> |
| | </pre> |
| |
|
| | |
| | <h2>ConfirmWithUser</h2> |
| | <div class="sig">Function ConfirmWithUser( prompt )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Shows a Yes/No popup dialog to the engineer and returns <code>True</code> if they clicked Yes, |
| | <code>False</code> if they clicked No or pressed Cancel. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Function</span> ConfirmWithUser(prompt) |
| | returnValue = MsgBox(prompt, 4, <span class="string">"Confirm:"</span>) |
| | <span class="comment">' MsgBox with parameter 4 = Yes/No buttons |
| | ' Return value 6 = Yes was clicked, anything else = No</span> |
| | ConfirmWithUser = (returnValue = 6) <span class="comment">' Returns True only if Yes was clicked</span> |
| | <span class="keyword">End Function</span> |
| | </pre> |
| |
|
| | |
| | <h2>GoOnline</h2> |
| | <div class="sig">Function GoOnline( TargetStateOnline )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Puts the Starter project <em>online</em> (connected to physical drives) or |
| | <em>offline</em> (disconnected). Returns <code>True</code> if the target state was achieved.<br><br> |
| | It always goes offline first before going online — this prevents communication conflicts. |
| | </div> |
| |
|
| | <div class="warn"> |
| | <strong>⚠️ Why go offline first?</strong> |
| | Siemens Starter can sometimes get "stuck" if you try to go online while already online. |
| | Going offline first resets the connection state cleanly. |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Function</span> GoOnline(TargertStateOnline) <span class="comment">' Returns True if the requested state was reached</span> |
| | ThisProject.Online = <span class="keyword">False</span> <span class="comment">' ALWAYS go offline first (resets connection state)</span> |
| | On Error Resume Next <span class="comment">' Don't crash if going online fails</span> |
| |
|
| | <span class="keyword">If</span> TargertStateOnline <span class="keyword">Then</span> |
| | ThisProject.Online = <span class="keyword">True</span> <span class="comment">' Try to go online (connect to real hardware)</span> |
| | <span class="keyword">Else</span> |
| | ThisProject.Online = <span class="keyword">False</span> <span class="comment">' Explicitly stay offline</span> |
| | <span class="keyword">End If</span> |
| |
|
| | GoOnline = ThisProject.Online <span class="comment">' Return the actual current state |
| | ' (may be False even if True was requested, if connection failed)</span> |
| | On Error GoTo 0 |
| | <span class="keyword">End Function</span> |
| | </pre> |
| |
|
| | |
| | <h2>CheckExists</h2> |
| | <div class="sig">Function CheckExists( DeviceName, ThisName, ThisType, Alert )</div> |
| |
|
| | <div class="explain"> |
| | <strong>What it does:</strong><br> |
| | Checks whether a given device, drive object, or sub-object exists in the Starter project. |
| | Returns <code>True</code> if found, <code>False</code> if not. |
| | Optionally shows an error popup if it's missing (<code>Alert = True</code>). |
| | </div> |
| |
|
| | <table> |
| | <tr><th>ThisType</th><th>Checks for</th></tr> |
| | <tr><td>0</td><td>A Device (e.g., a CU320)</td></tr> |
| | <tr><td>1</td><td>A Technology Object inside a device (e.g., the Unwind drive)</td></tr> |
| | <tr><td>2</td><td>A SubObject inside a device (e.g., an infeed module)</td></tr> |
| | </table> |
| |
|
| | <pre class="code"><span class="keyword">Function</span> CheckExists(DeviceName, ThisName, ThisType, Alert) |
| | On Error Resume Next |
| | <span class="keyword">If</span> ThisType=<span class="number">0</span> <span class="keyword">Then</span> Set ThisObject = PROJ.Devices(DeviceName) <span class="comment">' Try to get device</span> |
| | <span class="keyword">If</span> ThisType=<span class="number">1</span> <span class="keyword">Then</span> Set ThisObject = PROJ.Devices(DeviceName).TOs(ThisName) <span class="comment">' Try to get drive TO</span> |
| | <span class="keyword">If</span> ThisType=<span class="number">2</span> <span class="keyword">Then</span> Set ThisObject = PROJ.Devices(DeviceName).SubObjects(ThisName) |
| |
|
| | <span class="keyword">If</span> Err.Number <span class="keyword">Then</span> <span class="comment">' If any of the above failed (object not found)...</span> |
| | CheckExists = <span class="keyword">False</span> <span class="comment">' ...return False</span> |
| | <span class="keyword">If</span> Alert ... <span class="keyword">Then</span> MsgBox(...) <span class="comment">' ...and optionally show an error popup</span> |
| | <span class="keyword">Else</span> |
| | CheckExists = <span class="keyword">True</span> <span class="comment">' Object was found OK</span> |
| | <span class="keyword">End If</span> |
| | On Error GoTo 0 |
| | <span class="keyword">End Function</span> |
| | </pre> |
| |
|
| | |
| | <h2>EchoToScreen, StatusLine, StatusLineEcho</h2> |
| |
|
| | <div class="explain"> |
| | <strong>What they do:</strong><br> |
| | These three functions update the HTML UI to show progress and status messages. |
| | <ul> |
| | <li><code>EchoToScreen(text)</code> — prints a message to the browser's status bar (bottom of window)</li> |
| | <li><code>StatusLine(lineNumber, colour, text)</code> — sets the text and colour of the status cell next to a step button (e.g., turns "Step 1" green with "Complete")</li> |
| | <li><code>StatusLineEcho</code> — calls both StatusLine and EchoToScreen at the same time</li> |
| | </ul> |
| | </div> |
| |
|
| | <pre class="code"><span class="keyword">Sub</span> EchoToScreen(TextLine) |
| | APP.PrintToLog LineString <span class="comment">' Print a divider line to Starter's internal log (for readability)</span> |
| | MainPage.StatusText = LineString |
| | APP.PrintToLog TextLine <span class="comment">' Print the actual message</span> |
| | MainPage.StatusText = TextLine <span class="comment">' Display in the browser window's status bar</span> |
| | <span class="keyword">End Sub</span> |
| |
|
| | <span class="keyword">Sub</span> StatusLine(LineNumber, Colour, StatusText) |
| | <span class="comment">' Sets the text INSIDE the status cell next to button number LineNumber</span> |
| | <span class="comment">' e.g. StatusLine(1, "Green", "Complete") → step 1's status cell shows "Complete" in green</span> |
| | MainPage.Document.GetElementbyid(<span class="string">"out_"</span> & CStr(LineNumber)).innerHTML = StatusText |
| | MainPage.Document.GetElementbyid(<span class="string">"out_"</span> & CStr(LineNumber)).style.Color = Colour |
| | <span class="keyword">End Sub</span> |
| |
|
| | <span class="keyword">Sub</span> StatusLineEcho(LineNumber, Colour, StatusText) |
| | MainPage.StatusText = StatusText <span class="comment">' Update browser status bar</span> |
| | StatusLine LineNumber, Colour, StatusText <span class="comment">' Also update the button's status cell</span> |
| | <span class="keyword">End Sub</span> |
| | </pre> |
| |
|
| | <hr> |
| | <p style="color:#888; font-size:12px;"> |
| | Continue to <a href="03_drive_configuration.html">③ Drive Configuration</a>. |
| | </p> |
| |
|
| | </body> |
| | </html> |
| |
|