File size: 22,967 Bytes
f6f4993 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | <!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<>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>
<!-- ===== 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 & <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>
<!-- ===== 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 & <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>
<!-- ===== 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 & 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 <> 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>
<!-- ===== 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> & 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>
|