vab / 02_utility_functions.html
bardd's picture
Upload 8 files
f6f4993 verified
<!DOCTYPE html>
<!--
================================================================================
FILE: 02_utility_functions.html
PURPOSE: Documents the UTILITY FUNCTIONS used throughout the script.
These are building blocks called by the higher-level configuration subs.
FUNCTIONS COVERED:
- CreateLogFile (line 202) — creates the LogFile script in Starter
- PrintToLogFile (line 219) — appends text to the LogFile script
- SetParam (line 228) — writes a parameter to a drive object and logs it
- Padding_Text (line 251) — pads a string to a fixed width for aligned log output
- PrintHeader (line 270) — writes a table header row to the log file
- SetSym (line 299) — writes a symbol (named alias) to a drive object and logs it
- ConfirmWithUser (line 343) — shows a Yes/No popup dialog
- GoOnline (line 348) — takes the Starter project online or offline
- CheckExists (line 704) — checks if a device/TO object exists in the project
- EchoToScreen (line 2382) — prints a message to the HTML status bar
- StatusLine (line 2394) — sets text/colour of a step's status cell
- StatusLineEcho (line 2399) — StatusLine + EchoToScreen combined
================================================================================
-->
<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>
<!-- ===== CreateLogFile ===== -->
<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&lt;&gt;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>&amp;<span class="string">"-------------------- LogFile created -------------------"</span>&amp;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>
<!-- ===== PrintToLogFile ===== -->
<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 &amp; <span class="string">"'"</span> &amp; logText &amp; 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>
<!-- ===== SetParam ===== -->
<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 &amp; <span class="string">","</span> &amp; ParameterType &amp; <span class="string">","</span> &amp; Parameter &amp; <span class="string">","</span> &amp; Index &amp; <span class="string">","</span> &amp; oldValue &amp; <span class="string">","</span> &amp; NewValue &amp; <span class="string">","</span> &amp; FinalValue)
<span class="keyword">End If</span>
PrintToLogFile(DriveObjectName &amp; <span class="string">","</span> &amp; ParameterType &amp; <span class="string">","</span> &amp; Parameter &amp; <span class="string">","</span> &amp; Index &amp; <span class="string">","</span> &amp; OldValue &amp; <span class="string">","</span> &amp; NewValue &amp; <span class="string">","</span> &amp; FinalValue)
<span class="comment">' Log line example: "Unwind ,Parameter ,210 ,0 ,0 ,400 ,400 "</span>
<span class="keyword">End Sub</span>
</pre>
<!-- ===== Padding_Text ===== -->
<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 &amp; 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>
<!-- ===== SetSym ===== -->
<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 &lt;&gt; 0 <span class="keyword">And</span> IndexEndPosn &lt;&gt; 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>
<!-- ===== ConfirmWithUser ===== -->
<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>
<!-- ===== GoOnline ===== -->
<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>
<!-- ===== CheckExists ===== -->
<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>
<!-- ===== EchoToScreen / StatusLine ===== -->
<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> &amp; CStr(LineNumber)).innerHTML = StatusText
MainPage.Document.GetElementbyid(<span class="string">"out_"</span> &amp; 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>