<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.1">Jekyll</generator><link href="https://cookncode.com/feed/twincat.xml" rel="self" type="application/atom+xml" /><link href="https://cookncode.com/" rel="alternate" type="text/html" /><updated>2026-04-14T18:47:25+00:00</updated><id>https://cookncode.com/feed/twincat.xml</id><title type="html">Cook &amp;amp; Code | Twincat</title><subtitle>Personal projects and ideas.</subtitle><entry><title type="html">Playing with Chat-GPT and structured text</title><link href="https://cookncode.com/twincat/2023/01/19/chat-gpt.html" rel="alternate" type="text/html" title="Playing with Chat-GPT and structured text" /><published>2023-01-19T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2023/01/19/chat-gpt</id><content type="html" xml:base="https://cookncode.com/twincat/2023/01/19/chat-gpt.html"><![CDATA[<p><a href="https://chat.openai.com/">Chat-GPT</a> is OpenAI’s latest release of it GPT series language models. Its capabilities have surpassed a threshold that made it famous outside of the IT crowd. In the mean time, it has gathered many supporters and possibly even more haters. Let me test its ability to generate structured text.</p>

<p><strong>Prompt:</strong> <em>write a function in structured text which replaces all vowels with the numbers 1-5.</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION ReplaceVowels(s: STRING) : STRING
VAR
    i: INT;
    vowels: ARRAY[1..5] OF CHAR;
BEGIN
    vowels[1] := 'a';
    vowels[2] := 'e';
    vowels[3] := 'i';
    vowels[4] := 'o';
    vowels[5] := 'u';

    FOR i := 1 TO LEN(s) DO
        IF (s[i] = vowels[1]) THEN s[i] := '1';
        IF (s[i] = vowels[2]) THEN s[i] := '2';
        IF (s[i] = vowels[3]) THEN s[i] := '3';
        IF (s[i] = vowels[4]) THEN s[i] := '4';
        IF (s[i] = vowels[5]) THEN s[i] := '5';
    END_FOR;

    ReplaceVowels := s;
END_FUNCTION
</code></pre></div></div>

<blockquote>
  <p>This function takes a string as input, and replaces all vowels with the numbers 1-5. The function first declares an array of characters called “vowels” which contains all the vowels. Then it loops through each character of the input string, and checks if the current character is equal to any of the vowels in the array. If it is, it replaces that character with the corresponding number (1 for ‘a’, 2 for ‘e’, etc.). Finally, the function returns the modified string.</p>
</blockquote>

<p>Pretty good! Not valid structured text, but it’s close. Lets try to call the function anyway, to see if it works.</p>

<p><strong>Prompt:</strong> <em>ReplaceVowels(“Apple”)</em></p>

<blockquote>
  <p>If the input string is “Apple” and you call the ReplaceVowels function with “Apple” as the argument, the function will loop through each character of the input string and check if it is a vowel. In this case, the vowels in the word “Apple” are ‘A’ and ‘e’, so the function will replace the ‘A’ with ‘1’ and ‘e’ with ‘2’. So the output will be “1ppl2”</p>
</blockquote>

<p>It is quite verbose, but it gave the correct answer! Not bad. Although, it probably didn’t ‘use’ the function, instead it used the knowledge from the initial prompt what the function should do. Let’s optimize the function a little.</p>

<p><strong>Prompt:</strong> <em>modify the function such that you use a switch case instead of IF ELSE statements</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION ReplaceVowels(s: STRING) : STRING
VAR
    i: INT;
BEGIN
    FOR i := 1 TO LEN(s) DO
        CASE s[i] OF
            'a': s[i] := '1';
            'e': s[i] := '2';
            'i': s[i] := '3';
            'o': s[i] := '4';
            'u': s[i] := '5';
        END_CASE;
    END_FOR;

    ReplaceVowels := s;
END_FUNCTION
</code></pre></div></div>

<blockquote>
  <p>This function uses a switch case statement instead of IF ELSE statements to check if a character is a vowel and replace it with the corresponding number. The switch case statement checks the value of the current character in the input string (s[i]), and replaces it with the corresponding number based on the case that matches the character.</p>
</blockquote>

<p>Very nice. Next improvement.</p>

<p><strong>Prompt:</strong> <em>use a new string to save the modified string into</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION ReplaceVowels(s: STRING) : STRING
VAR
    i: INT;
    newStr: STRING;
BEGIN
    newStr := '';
    FOR i := 1 TO LEN(s) DO
        CASE s[i] OF
            'a': newStr := newStr + '1';
            'e': newStr := newStr + '2';
            'i': newStr := newStr + '3';
            'o': newStr := newStr + '4';
            'u': newStr := newStr + '5';
            ELSE newStr := newStr + s[i];
        END_CASE;
    END_FOR;

    ReplaceVowels := newStr;
END_FUNCTION
</code></pre></div></div>

<p>Cool.</p>

<p><strong>Prompt:</strong> <em>you can’t concatenate strings with a + in structured text. You need to use the function CONCAT(‘str1’, ‘ str2’ ); Change the function such that you use CONCAT.</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION ReplaceVowels(s: STRING) : STRING
VAR
    i: INT;
    newStr: STRING;
BEGIN
    newStr := '';
    FOR i := 1 TO LEN(s) DO
        CASE s[i] OF
            'a': newStr := CONCAT(newStr, '1');
            'e': newStr := CONCAT(newStr, '2');
            'i': newStr := CONCAT(newStr, '3');
            'o': newStr := CONCAT(newStr, '4');
            'u': newStr := CONCAT(newStr, '5');
            ELSE newStr := CONCAT(newStr, s[i]);
        END_CASE;
    END_FOR;

    ReplaceVowels := newStr;
END_FUNCTION
</code></pre></div></div>

<p>Impressive! now lets try to get and <code class="language-plaintext highlighter-rouge">END_VAR</code> in there.</p>

<p><strong>Prompt:</strong> <em>you forgot the END_VAR</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION ReplaceVowels(s: STRING) : STRING
VAR
    i: INT;
    newStr: STRING;
BEGIN
    newStr := '';
    FOR i := 1 TO LEN(s) DO
        CASE s[i] OF
            'a': newStr := CONCAT(newStr, '1');
            'e': newStr := CONCAT(newStr, '2');
            'i': newStr := CONCAT(newStr, '3');
            'o': newStr := CONCAT(newStr, '4');
            'u': newStr := CONCAT(newStr, '5');
            ELSE newStr := CONCAT(newStr, s[i]);
        END_CASE;
    END_FOR;

    ReplaceVowels := newStr;
END_FUNCTION
END_VAR
</code></pre></div></div>

<p>Uh-oh. This is where it starts to fail. Maybe If I’m more explicit.</p>

<p><strong>Prompt:</strong> <em>the end_var should come before BEGIN. Also with twincat structed text, the input variables need to be placed between VAR INPUT and END_VAR, not in brackets after the function name,</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VAR_INPUT
    s: STRING;
END_VAR

VAR
    i: INT;
    newStr: STRING;

FUNCTION ReplaceVowels() : STRING
BEGIN
    newStr := '';
    FOR i := 1 TO LEN(s) DO
        CASE s[i] OF
            'a': newStr := CONCAT(newStr, '1');
            'e': newStr := CONCAT(newStr, '2');
            'i': newStr := CONCAT(newStr, '3');
            'o': newStr := CONCAT(newStr, '4');
            'u': newStr := CONCAT(newStr, '5');
            ELSE newStr := CONCAT(newStr, s[i]);
        END_CASE;
    END_FOR;

    ReplaceVowels := newStr;
END_FUNCTION
</code></pre></div></div>

<p>It is becoming a hot mess. It forgot one <code class="language-plaintext highlighter-rouge">END_VAR</code> and moved the FUNCTION too far down. One more attempt:</p>

<p><strong>Prompt:</strong> <em>The FUNCTION declaration should go at the top. Followed by all VAR* blocks. Each VAR* block should be closed with END_VAR. And remove the parentheses after function name</em></p>

<blockquote>
  <p>Too many requests in 1 hour. Try again later.</p>
</blockquote>

<p>¯\_(ツ)_/¯</p>

<p>I guess it’s time to call it a day. Quite impressive none-the-less what it showed there. It got 90% right, and might save you some typing and thinking. What’s your experience?</p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Chat-GPT is OpenAI’s latest release of it GPT series language models. Its capabilities have surpassed a threshold that made it famous outside of the IT crowd. In the mean time, it has gathered many supporters and possibly even more haters. Let me test its ability to generate structured text.]]></summary></entry><entry><title type="html">How to use MC_TorqueControl?</title><link href="https://cookncode.com/twincat/2022/12/10/torque_control.html" rel="alternate" type="text/html" title="How to use MC_TorqueControl?" /><published>2022-12-10T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/12/10/torque_control</id><content type="html" xml:base="https://cookncode.com/twincat/2022/12/10/torque_control.html"><![CDATA[<p><a href="https://infosys.beckhoff.com/content/1033/tcplclib_tc2_mc2/7617393803.html?id=6677792901421113137"><code class="language-plaintext highlighter-rouge">MC_TorqueControl</code></a> is a method to force or torque control an axis. In this article, I show how to set up a simple project and use this function.</p>

<p><em>There is also a <a href="https://www.youtube.com/watch?v=Lw-yW4OdtZA">YouTube video</a> by Electrical Automation Hands-On who explains these steps.</em></p>

<h2 id="what-is-mc_torquecontrol">What is <code class="language-plaintext highlighter-rouge">MC_TorqueControl</code>?</h2>

<p>Other than the name suggests, this method can be used for both linear axes as a force controller or rotational axis as a torque controller. Thus, you can set a force or torque and the drive ensures that your motor keeps that force or torque. To avoid saying torque <em>and</em> force each time, I refer to both modes when I mention torque control.</p>

<p>Torque control is always used when you move an axis, but you don’t notice it. Depending on the selected mode, between one and three different cascaded PID controllers are used.</p>

<p>Below is the diagram from the <strong>Tune drive</strong> tab of Drive Manager 2. When you use torque control, <code class="language-plaintext highlighter-rouge">Tset</code> is directly fed into the current controller. But once you select the velocity mode, the velocity controller block connects to the current controller one. Then <code class="language-plaintext highlighter-rouge">Tset</code> is no longer fed into the current controller, but instead, the output of the velocity controller is directly fed into it. A similar story occurs when you select position control. Then the output of the position controller connects to the velocity controller, ignoring the <code class="language-plaintext highlighter-rouge">Vset</code>.</p>

<p><a href="/assets/2022-torque-control/pid_torque_control.png"><picture><source srcset="/generated/2022-torque-control/pid_torque_control-300-285e5ec31.webp 300w, /generated/2022-torque-control/pid_torque_control-500-285e5ec31.webp 500w, /generated/2022-torque-control/pid_torque_control-720-285e5ec31.webp 720w" type="image/webp"><source srcset="/generated/2022-torque-control/pid_torque_control-300-38b0f4f10.png 300w, /generated/2022-torque-control/pid_torque_control-500-38b0f4f10.png 500w, /generated/2022-torque-control/pid_torque_control-720-38b0f4f10.png 720w" type="image/png"><img src="/generated/2022-torque-control/pid_torque_control-798-38b0f4f10.png" width="798" height="438"></picture></a></p>

<h2 id="how-to-use-torque-control">How to use torque control?</h2>

<p>Make sure you have:</p>

<ul>
  <li><a href="https://www.beckhoff.com/en-en/products/automation/twincat/texxxx-twincat-3-engineering/te5950.html">Drive Manager 2</a> installed</li>
  <li>TwinCAT 3.1.4024.15 for both XAE (engineering) and XAR (runtime) or later</li>
  <li>For AX5000: firmware 2.14 or later</li>
  <li>For AX8000: firmware 1.03 Build 002 or later.</li>
</ul>

<p>Then, follow these steps to use torque control:</p>

<ol>
  <li>In the <strong>Advanced &gt; Slot settings</strong> tab, select <strong>VelocityControl2</strong> for the <strong>VelocityControlSlot</strong> of the respective axis. This enables the velocity limit.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>
<a href="/assets/2022-torque-control/velocity_limit.png"><picture><source srcset="/generated/2022-torque-control/velocity_limit-300-39364b71d.webp 300w, /generated/2022-torque-control/velocity_limit-500-39364b71d.webp 500w, /generated/2022-torque-control/velocity_limit-658-39364b71d.webp 658w" type="image/webp"><source srcset="/generated/2022-torque-control/velocity_limit-300-d56e0276e.png 300w, /generated/2022-torque-control/velocity_limit-500-d56e0276e.png 500w, /generated/2022-torque-control/velocity_limit-658-d56e0276e.png 658w" type="image/png"><img src="/generated/2022-torque-control/velocity_limit-658-d56e0276e.png" width="658" height="436"></picture></a></li>
  <li>In the <strong>Parameter list</strong>, select <strong>TorqueMightBeReducedToZero (1)</strong> at index <strong>0x60CA</strong>. This is another setting to enable the velocity limit.
<a href="/assets/2022-torque-control/speed_limit.png"><picture><source srcset="/generated/2022-torque-control/speed_limit-300-21881ccb5.webp 300w, /generated/2022-torque-control/speed_limit-500-21881ccb5.webp 500w, /generated/2022-torque-control/speed_limit-720-21881ccb5.webp 720w" type="image/webp"><source srcset="/generated/2022-torque-control/speed_limit-300-7e1bb4e46.png 300w, /generated/2022-torque-control/speed_limit-500-7e1bb4e46.png 500w, /generated/2022-torque-control/speed_limit-720-7e1bb4e46.png 720w" type="image/png"><img src="/generated/2022-torque-control/speed_limit-800-7e1bb4e46.png" width="1046" height="469"></picture></a></li>
  <li>In the <strong>Process data</strong> tab, expand the <strong>Ch A operation</strong> and select <strong>Cyclic_synchronous_torque_mode_CST (10)</strong> for virtual operation mode 0. This adds the necessary process data objects, such as the target torque and the actual measured torque. It also enables you to switch between position and torque control.
<a href="/assets/2022-torque-control/torque_mode.png"><picture><source srcset="/generated/2022-torque-control/torque_mode-300-d8dc2f776.webp 300w, /generated/2022-torque-control/torque_mode-500-d8dc2f776.webp 500w, /generated/2022-torque-control/torque_mode-663-d8dc2f776.webp 663w" type="image/webp"><source srcset="/generated/2022-torque-control/torque_mode-300-c71a32032.png 300w, /generated/2022-torque-control/torque_mode-500-c71a32032.png 500w, /generated/2022-torque-control/torque_mode-663-c71a32032.png 663w" type="image/png"><img src="/generated/2022-torque-control/torque_mode-663-c71a32032.png" width="663" height="247"></picture></a></li>
  <li>In the <strong>MOTION &gt; NC- TASK1 SAF &gt; Axis &gt; {your axis} &gt; Encoder</strong> and <strong>Drive</strong> tabs, turn on Time compensation. This setting enables smooth switching between position and torque mode. That is because, in torque mode, the NC axis writes permanent actual positions into the set position variable. Due to a dead time, the actual is delayed by four cycles. This dead time can then be compensated by enabling time compensation. This is relevant when you switch back into position mode.
<a href="/assets/2022-torque-control/time_compensation_encoder.png"><picture><source srcset="/generated/2022-torque-control/time_compensation_encoder-300-399647cab.webp 300w, /generated/2022-torque-control/time_compensation_encoder-500-399647cab.webp 500w, /generated/2022-torque-control/time_compensation_encoder-720-399647cab.webp 720w" type="image/webp"><source srcset="/generated/2022-torque-control/time_compensation_encoder-300-8d5d74b91.png 300w, /generated/2022-torque-control/time_compensation_encoder-500-8d5d74b91.png 500w, /generated/2022-torque-control/time_compensation_encoder-720-8d5d74b91.png 720w" type="image/png"><img src="/generated/2022-torque-control/time_compensation_encoder-800-8d5d74b91.png" width="1185" height="360"></picture></a>
<a href="/assets/2022-torque-control/time_compensation_drive.png"><picture><source srcset="/generated/2022-torque-control/time_compensation_drive-300-cf29666fa.webp 300w, /generated/2022-torque-control/time_compensation_drive-500-cf29666fa.webp 500w, /generated/2022-torque-control/time_compensation_drive-720-cf29666fa.webp 720w" type="image/webp"><source srcset="/generated/2022-torque-control/time_compensation_drive-300-730062dd3.png 300w, /generated/2022-torque-control/time_compensation_drive-500-730062dd3.png 500w, /generated/2022-torque-control/time_compensation_drive-720-730062dd3.png 720w" type="image/png"><img src="/generated/2022-torque-control/time_compensation_drive-800-730062dd3.png" width="1187" height="361"></picture></a></li>
</ol>

<p>After applying the settings, you can add the following code to force control an axis. Here</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">someAxis</code>: is the axis you want to control.</li>
  <li><code class="language-plaintext highlighter-rouge">forceOptions</code>: defines a starting value of the torque when the <code class="language-plaintext highlighter-rouge">force</code> function block is activated. This ensures a smooth transition from the previous mode.</li>
  <li><code class="language-plaintext highlighter-rouge">targetForce</code>: the torque you want to apply.</li>
  <li><code class="language-plaintext highlighter-rouge">TORQUE_RAMP</code>: defines a maximum ramp for the torque.</li>
  <li><code class="language-plaintext highlighter-rouge">VELOCITY_LIMIT</code>: Limit the velocity.</li>
  <li><code class="language-plaintext highlighter-rouge">ContinuousUpdate</code>: If <code class="language-plaintext highlighter-rouge">TRUE</code>, then changes to <code class="language-plaintext highlighter-rouge">targetForce</code> are immediately applied by torque control, rather than needing to toggle <code class="language-plaintext highlighter-rouge">force.Execute</code>.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>
Then you activate force control by setting <code class="language-plaintext highlighter-rouge">forceMode</code> to <code class="language-plaintext highlighter-rouge">TRUE</code>.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
    someAxis : Tc2_MC2.AXIS_REF;
    forceOptions : Tc2_MC2.ST_TorqueControlOptions := (EnableManualTorqueStartValue := TRUE);
    force : Tc2_MC2.MC_TorqueControl;
    forceMode : BOOL;
    targetForce : LREAL := 10;
END_VAR
VAR CONSTANT
	TORQUE_RAMP : LREAL := 50_000;
	VELOCITY_LIMIT : LREAL := 10_000;
END_VAR

forceOptions.ManualTorqueStartValue := someAxis.NcToPlc.ActTorque;

force(
	Execute:=forceMode,
    ContinuousUpdate:=forceMode,
    Relative:=FALSE,
    Torque:=targetForce,
    Axis:=someAxis,
    Options:=forceOptions,
    TorqueRamp:=TORQUE_RAMP,
    VelocityLimitHigh:=VELOCITY_LIMIT,
    VelocityLimitLow:=VELOCITY_LIMIT * -1,
);
</code></pre></div></div>

<p>After calling another motion function block, the operation mode is automatically switched back from torque mode into position mode. For example <code class="language-plaintext highlighter-rouge">MC_MoveVelocity</code>, <code class="language-plaintext highlighter-rouge">MC_MoveAbsolute</code>, <code class="language-plaintext highlighter-rouge">MC_Halt</code> etc.</p>

<p>Some final notes:</p>

<ul>
  <li>Lag monitoring switches off automatically if you use <code class="language-plaintext highlighter-rouge">MC_TorqueControl</code> with the AX5000.</li>
  <li><code class="language-plaintext highlighter-rouge">MC_TorqueControl</code> works with all SoE or CoE drives, but maybe the velocity limit function doesn’t work.</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Velocity limits work with the AX5000 and the AX8000. But, because this limit is a feature inside the AX firmware it doesn’t work for the EL, ELM, or AMI. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://cookncode.com/TwinCatChangelog/tc3/#features">For continuous force mode to work, the TwinCAT runtime needs to be at version &gt;=4024.35</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[MC_TorqueControl is a method to force or torque control an axis. In this article, I show how to set up a simple project and use this function.]]></summary></entry><entry><title type="html">TwinCAT datatypes in .NET</title><link href="https://cookncode.com/twincat/2022/10/03/data-types.html" rel="alternate" type="text/html" title="TwinCAT datatypes in .NET" /><published>2022-10-03T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/10/03/data-types</id><content type="html" xml:base="https://cookncode.com/twincat/2022/10/03/data-types.html"><![CDATA[<p>When you’re working with ADS, you often need to know the size of a data type or what a data type is called in your .NET language. I couldn’t find the combined information, so I decided to make the overview myself.</p>

<table>
  <thead>
    <tr>
      <th>IEC61131-3</th>
      <th>System Manager</th>
      <th>.NET type</th>
      <th>C#</th>
      <th>Visual Basic</th>
      <th>Lower bound</th>
      <th>Upper bound</th>
      <th>Memory space</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BOOL</code></td>
      <td><code class="language-plaintext highlighter-rouge">BIT</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Boolean</code></td>
      <td><code class="language-plaintext highlighter-rouge">bool</code></td>
      <td><code class="language-plaintext highlighter-rouge">Boolean</code></td>
      <td>0 (<code class="language-plaintext highlighter-rouge">FALSE</code>)</td>
      <td>1 (<code class="language-plaintext highlighter-rouge">TRUE</code>)</td>
      <td>8 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BOOL</code></td>
      <td><code class="language-plaintext highlighter-rouge">BIT8</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Boolean</code></td>
      <td><code class="language-plaintext highlighter-rouge">bool</code></td>
      <td><code class="language-plaintext highlighter-rouge">Boolean</code></td>
      <td>0 (<code class="language-plaintext highlighter-rouge">FALSE</code>)</td>
      <td>1 (<code class="language-plaintext highlighter-rouge">TRUE</code>)</td>
      <td>8 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BYTE</code></td>
      <td><code class="language-plaintext highlighter-rouge">BITARR8</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Byte</code></td>
      <td><code class="language-plaintext highlighter-rouge">byte</code></td>
      <td><code class="language-plaintext highlighter-rouge">Byte</code></td>
      <td>0</td>
      <td>255</td>
      <td>8 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">WORD</code></td>
      <td><code class="language-plaintext highlighter-rouge">BITARR16</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.UInt16</code></td>
      <td><code class="language-plaintext highlighter-rouge">ushort</code></td>
      <td>-</td>
      <td>0</td>
      <td>65535</td>
      <td>16 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DWORD</code></td>
      <td><code class="language-plaintext highlighter-rouge">BITARR32</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.UInt32</code></td>
      <td><code class="language-plaintext highlighter-rouge">uint</code></td>
      <td>-</td>
      <td>0</td>
      <td>4.29·10<sup>9</sup></td>
      <td>32 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">INT8</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.SByte</code></td>
      <td><code class="language-plaintext highlighter-rouge">sbyte</code></td>
      <td>-</td>
      <td>-128</td>
      <td>127</td>
      <td>8 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">INT</code></td>
      <td><code class="language-plaintext highlighter-rouge">INT16</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Int16</code></td>
      <td><code class="language-plaintext highlighter-rouge">short</code></td>
      <td><code class="language-plaintext highlighter-rouge">Short</code></td>
      <td>-32768</td>
      <td>32767</td>
      <td>16 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">INT32</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Int32</code></td>
      <td><code class="language-plaintext highlighter-rouge">int</code></td>
      <td><code class="language-plaintext highlighter-rouge">Integer</code></td>
      <td>-2.15·10<sup>9</sup></td>
      <td>2.15·10<sup>9</sup></td>
      <td>32 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">LINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">INT64</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Int64</code></td>
      <td><code class="language-plaintext highlighter-rouge">long</code></td>
      <td><code class="language-plaintext highlighter-rouge">Long</code></td>
      <td>-2<sup>63</sup></td>
      <td>-2<sup>63</sup>-1</td>
      <td>64 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">USINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">UINT8</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Byte</code></td>
      <td><code class="language-plaintext highlighter-rouge">byte</code></td>
      <td><code class="language-plaintext highlighter-rouge">Byte</code></td>
      <td>0</td>
      <td>255</td>
      <td>8 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">UINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">UINT16</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.UInt16</code></td>
      <td><code class="language-plaintext highlighter-rouge">ushort</code></td>
      <td>-</td>
      <td>0</td>
      <td>65535</td>
      <td>16 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">UDINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">UINT32</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.UInt32</code></td>
      <td><code class="language-plaintext highlighter-rouge">uint</code></td>
      <td>-</td>
      <td>0</td>
      <td>4.29·10<sup>9</sup></td>
      <td>32 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ULINT</code></td>
      <td><code class="language-plaintext highlighter-rouge">UINT64</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.UInt64</code></td>
      <td><code class="language-plaintext highlighter-rouge">ulong</code></td>
      <td>-</td>
      <td>0</td>
      <td>2<sup>64</sup>-1</td>
      <td>64 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REAL</code></td>
      <td><code class="language-plaintext highlighter-rouge">FLOAT</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Single</code></td>
      <td><code class="language-plaintext highlighter-rouge">float</code></td>
      <td><code class="language-plaintext highlighter-rouge">Single</code></td>
      <td>-3.40·10<sup>38</sup></td>
      <td>3.40·10<sup>38</sup></td>
      <td>32 bit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">LREAL</code></td>
      <td><code class="language-plaintext highlighter-rouge">DOUBLE</code></td>
      <td><code class="language-plaintext highlighter-rouge">System.Double</code></td>
      <td><code class="language-plaintext highlighter-rouge">double</code></td>
      <td><code class="language-plaintext highlighter-rouge">Double</code></td>
      <td>-1.798·10<sup>308</sup></td>
      <td>1.798·10<sup>308</sup></td>
      <td>64 bit</td>
    </tr>
  </tbody>
</table>

<h2 id="sources">Sources</h2>

<ul>
  <li><a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529394315.html?id=1768255288341275228">BOOL</a></li>
  <li><a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529399691.html&amp;id=">INT</a></li>
  <li><a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529399691.html&amp;id=">REAL</a></li>
  <li><a href="https://infosys.beckhoff.com/content/1033/tc3_system/html/tcsysmgr_datatypecomparison.htm?id=3043404538898382042">Data type comparison</a></li>
</ul>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[When you’re working with ADS, you often need to know the size of a data type or what a data type is called in your .NET language. I couldn’t find the combined information, so I decided to make the overview myself.]]></summary></entry><entry><title type="html">Why am I getting a page fault in TwinCAT?</title><link href="https://cookncode.com/twincat/2022/09/18/page-fault.html" rel="alternate" type="text/html" title="Why am I getting a page fault in TwinCAT?" /><published>2022-09-18T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/09/18/page-fault</id><content type="html" xml:base="https://cookncode.com/twincat/2022/09/18/page-fault.html"><![CDATA[<p><a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">Earlier</a> I talked about how you can prevent page faults from references. In this post, I try to show a complete overview of page fault origins and how to prevent them.</p>

<ul>
  <li>Example code: <a href="https://github.com/Roald87/TwincatTutorials/tree/main/PageFaults">GitHub</a>/<a href="https://downgit.github.io/#/home?url=https://github.com/Roald87/TwincatTutorials/tree/main/PageFaults">Direct download</a> with TwinCAT 4024.25</li>
</ul>

<h2 id="page-faults-in-twincat">Page faults in TwinCAT</h2>

<p>You probably came across the following error message when you activated a configuration. The error says there is a <em>Page Fault</em>. This message was always quite puzzling to me when I started programming PLCs.</p>

<blockquote>
  <p>Exception (Exception Code: Oxc0000005, Page Fault) in PLC Application PLC Instance, Task PlcTask (RBP: Oxffffc80154efece0, RIP: 0xfiff800f2741d9a1, RSP: Oxffffc80154efecb0)</p>
</blockquote>

<p><a href="/assets/2021-02-07-preventing-page-faults-from-references/page_fault.png"><picture><source srcset="/generated/2021-02-07-preventing-page-faults-from-references/page_fault-300-678e86881.webp 300w, /generated/2021-02-07-preventing-page-faults-from-references/page_fault-500-678e86881.webp 500w, /generated/2021-02-07-preventing-page-faults-from-references/page_fault-540-678e86881.webp 540w" type="image/webp"><source srcset="/generated/2021-02-07-preventing-page-faults-from-references/page_fault-300-50ce8c962.png 300w, /generated/2021-02-07-preventing-page-faults-from-references/page_fault-500-50ce8c962.png 500w, /generated/2021-02-07-preventing-page-faults-from-references/page_fault-540-50ce8c962.png 540w" type="image/png"><img src="/generated/2021-02-07-preventing-page-faults-from-references/page_fault-540-50ce8c962.png" width="540" height="222"></picture></a></p>

<p>The reason for the page fault is easy to see once you log into your project because it highlights the point of failure.</p>

<p><a href="/assets/2022-page-fault/pointer.png"><picture><source srcset="/generated/2022-page-fault/pointer-300-1d1b6009b.webp 300w, /generated/2022-page-fault/pointer-409-1d1b6009b.webp 409w" type="image/webp"><source srcset="/generated/2022-page-fault/pointer-300-3e6d71de3.png 300w, /generated/2022-page-fault/pointer-409-3e6d71de3.png 409w" type="image/png"><img src="/generated/2022-page-fault/pointer-409-3e6d71de3.png" width="409" height="147"></picture></a></p>

<h2 id="what-are-page-faults">What are page faults?</h2>

<p><a href="https://en.wikipedia.org/wiki/Page_fault">Wikipedia lists different types of page faults</a>. I think the ones you get in TwinCAT are of the invalid type. This type of page fault is caused by a reference to an invalid memory address. Let me show through three different examples what that means.</p>

<h2 id="pointers">Pointers</h2>

<p><a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529453451.html?id=5839194631499501145">Pointers</a> store the address of a variable. When you instantiate a pointer to a variable, the default address is 0. Therefore, in the example below <code class="language-plaintext highlighter-rouge">pointerToNumber</code> is 0. Then in the implementation part, I try to save the value to which the pointer points into <code class="language-plaintext highlighter-rouge">number</code> by dereferencing the pointer using the <code class="language-plaintext highlighter-rouge">^</code> symbol.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM PointerExample
VAR
    pointerToNumber : POINTER TO INT; // 0
    number : INT;
END_VAR

number := pointerToNumber^;
</code></pre></div></div>

<p>When you activate this code you get a page fault. If you log in, you see that the pointer has address 0. Because this is not a valid memory address an exception is raised and your code halts here.</p>

<p><a href="/assets/2022-page-fault/pointer.png"><picture><source srcset="/generated/2022-page-fault/pointer-300-1d1b6009b.webp 300w, /generated/2022-page-fault/pointer-409-1d1b6009b.webp 409w" type="image/webp"><source srcset="/generated/2022-page-fault/pointer-300-3e6d71de3.png 300w, /generated/2022-page-fault/pointer-409-3e6d71de3.png 409w" type="image/png"><img src="/generated/2022-page-fault/pointer-409-3e6d71de3.png" width="409" height="147"></picture></a></p>

<h3 id="solution">Solution</h3>

<p>The solution to prevent this is quite simple: check if the address is 0 before you try to dereference the pointer. The complete example becomes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM PointerExample
VAR
    pointerToNumber : POINTER TO INT;
    number : INT;
END_VAR

IF pointerToNumber &lt;&gt; 0 THEN
    number := pointerToNumber^;
END_IF
</code></pre></div></div>

<p>Although this solves your issue, there is a good chance that you forget to implement the check at least once. But, you can automatically check for valid pointers with the POU <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2530405259.html?id=7869750361486034578"><code class="language-plaintext highlighter-rouge">CheckPointer</code></a>.</p>

<p>The <code class="language-plaintext highlighter-rouge">CheckPointer</code> function is automatically called <em>each time</em> before you use a pointer. The advantage is that you can trigger an error message which makes diagnostics easier. A disadvantage is that you add an extra function call when you use a pointer. If you use a lot of pointers, the extra overhead might cause cycle overruns. Also, it can’t prevent the pointer call, so you get the page fault regardless.</p>

<p>To add the <code class="language-plaintext highlighter-rouge">CheckPointer</code> to your project, right click your PLC project and select <strong>Add &gt; POU for implicit checks</strong>.</p>

<p><a href="/assets/2022-page-fault/implicit_checks.png"><picture><source srcset="/generated/2022-page-fault/implicit_checks-300-9ceb0c59f.webp 300w, /generated/2022-page-fault/implicit_checks-500-9ceb0c59f.webp 500w, /generated/2022-page-fault/implicit_checks-720-9ceb0c59f.webp 720w" type="image/webp"><source srcset="/generated/2022-page-fault/implicit_checks-300-ce6ac06e7.png 300w, /generated/2022-page-fault/implicit_checks-500-ce6ac06e7.png 500w, /generated/2022-page-fault/implicit_checks-720-ce6ac06e7.png 720w" type="image/png"><img src="/generated/2022-page-fault/implicit_checks-721-ce6ac06e7.png" width="721" height="236"></picture></a></p>

<p>Select <strong>Pointer Check</strong> and confirm with <strong>Open</strong>.</p>

<p><a href="/assets/2022-page-fault/add_checkpointer.png"><picture><source srcset="/generated/2022-page-fault/add_checkpointer-300-30133d655.webp 300w, /generated/2022-page-fault/add_checkpointer-346-30133d655.webp 346w" type="image/webp"><source srcset="/generated/2022-page-fault/add_checkpointer-300-51a729a6e.png 300w, /generated/2022-page-fault/add_checkpointer-346-51a729a6e.png 346w" type="image/png"><img src="/generated/2022-page-fault/add_checkpointer-346-51a729a6e.png" width="346" height="480"></picture></a></p>

<p>This adds the <code class="language-plaintext highlighter-rouge">CheckPointer</code> function to your project and it already has a suggested implementation. If I run the failing example code, an error message is printed in the error console before it crashes.</p>

<p><a href="/assets/2022-page-fault/pointer_check_error_message.png"><picture><source srcset="/generated/2022-page-fault/pointer_check_error_message-300-42cee754a.webp 300w, /generated/2022-page-fault/pointer_check_error_message-500-42cee754a.webp 500w, /generated/2022-page-fault/pointer_check_error_message-720-42cee754a.webp 720w" type="image/webp"><source srcset="/generated/2022-page-fault/pointer_check_error_message-300-8c0ca43f7.png 300w, /generated/2022-page-fault/pointer_check_error_message-500-8c0ca43f7.png 500w, /generated/2022-page-fault/pointer_check_error_message-720-8c0ca43f7.png 720w" type="image/png"><img src="/generated/2022-page-fault/pointer_check_error_message-733-8c0ca43f7.png" width="733" height="77"></picture></a></p>

<p>Another solution would be to pass the pointer via <code class="language-plaintext highlighter-rouge">VAR_IN_OUT</code> or use constructor injection via <code class="language-plaintext highlighter-rouge">FB_init</code> as I showed in the <a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">earlier article</a>.</p>

<h2 id="references">References</h2>

<p>Another way you can get page faults is through references, as I also showed in an <a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">earlier article</a>. <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529458827.html?id=2716630061017907414">References</a> are pointers with an improved interface. Thus it shouldn’t come as a surprise that these can cause page faults as well.</p>

<p>The short example below causes a page fault: I instantiate a reference to an integer called <code class="language-plaintext highlighter-rouge">number</code>. Then I try to assign a number to this reference.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM ReferenceExample
VAR
    refNumber : REFERENCE TO INT;
END_VAR

refNumber := 1;
</code></pre></div></div>

<p>When you try to assign a number to this reference, you get a page fault, because <code class="language-plaintext highlighter-rouge">refNumber</code> doesn’t refer to anything.</p>

<p><a href="/assets/2022-page-fault/reference_page_fault.png"><picture><source srcset="/generated/2022-page-fault/reference_page_fault-300-e386d4336.webp 300w, /generated/2022-page-fault/reference_page_fault-483-e386d4336.webp 483w" type="image/webp"><source srcset="/generated/2022-page-fault/reference_page_fault-300-82872a0b0.png 300w, /generated/2022-page-fault/reference_page_fault-483-82872a0b0.png 483w" type="image/png"><img src="/generated/2022-page-fault/reference_page_fault-483-82872a0b0.png" width="483" height="130"></picture></a></p>

<h3 id="solution-1">Solution</h3>

<p>In the <a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">earlier article</a> I showed several ways you can prevent page faults from references. They were using:</p>

<ul>
  <li><a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529165707.html&amp;id="><code class="language-plaintext highlighter-rouge">__ISVALIDREF</code></a></li>
  <li>Adding references to <code class="language-plaintext highlighter-rouge">VAR_IN_OUT</code></li>
  <li>Or using constructor injection via <code class="language-plaintext highlighter-rouge">FB_init</code></li>
</ul>

<p>Here I only show the first solution with <code class="language-plaintext highlighter-rouge">__ISVALIDREF</code>. You use this function in a similar was as you would with the pointer check (<code class="language-plaintext highlighter-rouge">somePointer &lt;&gt; 0</code>): The full example becomes.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM ReferenceExample
VAR
    refNumber : REFERENCE TO INT;
END_VAR

IF __ISVALIDREF(refNumber) THEN
    refNumber := 1;
END_IF
</code></pre></div></div>

<p>The advantage of this is that you prevent page faults. But the reference never gets assigned, thus you might wonder why your code doesn’t do what you expect it to. In this case, it’s probably a good idea to add an <code class="language-plaintext highlighter-rouge">ELSE</code> clause with an appropriate error message. Even better would be to pass the reference via <code class="language-plaintext highlighter-rouge">VAR_IN_OUT</code> or <code class="language-plaintext highlighter-rouge">FB_init</code> if possible.</p>

<h2 id="interfaces">Interfaces</h2>

<p><a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/4256428299.html?id=507172925224818176">Interfaces</a> can also cause page faults as shown by this example. I defined an interface <code class="language-plaintext highlighter-rouge">I_Interface</code> with a single property called <code class="language-plaintext highlighter-rouge">SomeProperty</code> which returns an integer.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INTERFACE I_Interface

PROPERTY SomeProperty : INT

</code></pre></div></div>

<p>I create an instance <code class="language-plaintext highlighter-rouge">someInterface</code> of <code class="language-plaintext highlighter-rouge">I_Interface</code>. Then I try to save the integer returned by this into the variable <code class="language-plaintext highlighter-rouge">number</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM InterfaceExample
VAR
    number : INT;
    someInterface : I_Interface;
END_VAR

number := someInterface.SomeProperty;
</code></pre></div></div>

<p>Executing the code again results in a page fault. That is because a function block needs to be assigned to the interface first. Then <code class="language-plaintext highlighter-rouge">someInterface</code> contains the address to this function block. Without an assigned function block, the interface is 0. Since this is not a valid address you get a page fault.</p>

<p><a href="/assets/2022-page-fault/interface_page_fault.png"><picture><source srcset="/generated/2022-page-fault/interface_page_fault-300-7d745c2e7.webp 300w, /generated/2022-page-fault/interface_page_fault-434-7d745c2e7.webp 434w" type="image/webp"><source srcset="/generated/2022-page-fault/interface_page_fault-300-fb695d421.png 300w, /generated/2022-page-fault/interface_page_fault-434-fb695d421.png 434w" type="image/png"><img src="/generated/2022-page-fault/interface_page_fault-434-fb695d421.png" width="434" height="146"></picture></a></p>

<h3 id="solutions">Solutions</h3>

<p>The solution to preventing page faults from invalid interfaces is the same as for pointers: before you use the interface, check if it’s not 0.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM InterfaceExample
VAR
    number : INT;
    someInterface : I_Interface;
END_VAR

IF someInterface &lt;&gt; 0 THEN
    number := someInterface.SomeProperty;
END_IF
</code></pre></div></div>

<p>Again this solution silently fail, thus it might be wise to add an <code class="language-plaintext highlighter-rouge">ELSE</code> clause with an error message. Or if you use the interface in a function or function block you can use the <code class="language-plaintext highlighter-rouge">VAR_IN_OUT</code> or <code class="language-plaintext highlighter-rouge">FB_init</code> solutions mentioned in the <a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">earlier article</a>.</p>

<h2 id="conclusions">Conclusions</h2>

<p>I showed page faults can be caused by invalid pointers, references, and interfaces. For each case, I showed some solutions how to prevent the page faults, mainly by checking if the pointer or interface is not 0 or by using <code class="language-plaintext highlighter-rouge">__iSVALIDREF</code> for references.</p>

<p>Did I miss any cases which can cause page faults and what are your solutions to prevent PLC crashes from page faults? Let me know in the comments below.</p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Earlier I talked about how you can prevent page faults from references. In this post, I try to show a complete overview of page fault origins and how to prevent them.]]></summary></entry><entry><title type="html">TwinCAT/BSD installation and tutorial</title><link href="https://cookncode.com/twincat/2022/08/11/twincat-bsd.html" rel="alternate" type="text/html" title="TwinCAT/BSD installation and tutorial" /><published>2022-08-11T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/08/11/twincat-bsd</id><content type="html" xml:base="https://cookncode.com/twincat/2022/08/11/twincat-bsd.html"><![CDATA[<p>TwinCAT/BSD is a new operating system for the TwinCAT runtime. It has a small footprint and it’s free. Furthermore, TwinCAT/BSD allows you to run TwinCAT code locally when you have Hyper-V enabled if you install it on a virtual machine. In this tutorial, I go over the installation of TwinCAT/BSD on VMware and Virtual Box and show some basic usages of Tc/BSD.</p>

<h2 id="what-is-twincatbsd">What is TwinCAT/BSD?</h2>

<p>TwinCAT/BSD, or Tc/BSD for short, is an alternative operating system for TwinCAT. It’s based on the open source operating system FreeBSD. Beckhoff took FreeBSD and integrated the TwinCAT runtime into it. It serves as an alternative operating system for the TwinCAT runtime to Windows CE/7/10.</p>

<p>Note that TwinCAT/BSD is only for the TwinCAT runtime. Thus the place where your code gets executed. Nothing changes on the code development side: you still write your code on Windows in either Visual Studio or the TwinCAT XAE Shell.</p>

<p>Tc/BSD is a little different from Windows and it might take you some time to get used to it. For example, Tc/BSD doesn’t come with a desktop environment: there is no desktop with icons, wallpaper, and a start menu. If you start Tc/BSD, you see a black screen with white text on it: a terminal.</p>

<p>Make no mistake, working from a terminal can be quite powerful, but it can take some time to get familiar with. At the end of the tutorial, I show some examples of what you can do with it. You can also find some commands in <a href="https://ftp.beckhoff.com/download/document/ipc/embedded-pc/embedded-pc-cx/TwinCAT_BSD_en.pdf">the official manual</a> or in this <a href="https://github.com/benhar-dev/tcbsd-cheatsheet">cheat sheet</a>.</p>

<p>Some other advantages of using Tc/BSD are that it’s free and requires less space. Moreover, because Beckhoff has access to the code of the operating system, they do not rely on Microsoft for support. The reliance is causing issues for Windows CE because its end of life is planned for late 2023.</p>

<p>If you would like to get more details on Tc/BSD, watch the <a href="https://www.youtube.com/watch?v=az9vSr1GxE4">official Beckhoff video</a> or read <a href="https://ftp.beckhoff.com/download/document/ipc/embedded-pc/embedded-pc-cx/TwinCAT_BSD_en.pdf">the manual</a> for in-depth information.</p>

<h2 id="virtual-machines-️-hyper-v">Virtual machines ❤️ Hyper-V</h2>

<p>I started to look into TwinCAT/BSD, because I wanted to run some TwinCAT code locally. Earlier this was possible, but at a certain point, I installed <a href="https://www.docker.com/">Docker</a> which requires Hyper-V. Unfortunately, once you enable this and you try to run your TwinCAT code locally, you get an error:</p>

<p><a href="/assets/2022-tcbsd/hyperv_error.png"><picture><source srcset="/generated/2022-tcbsd/hyperv_error-300-7d40f40e8.webp 300w, /generated/2022-tcbsd/hyperv_error-359-7d40f40e8.webp 359w" type="image/webp"><source srcset="/generated/2022-tcbsd/hyperv_error-300-25e12b624.png 300w, /generated/2022-tcbsd/hyperv_error-359-25e12b624.png 359w" type="image/png"><img src="/generated/2022-tcbsd/hyperv_error-359-25e12b624.png" width="359" height="318"></picture></a></p>

<p>While researching how to circumvent this restriction, I came across a <a href="https://www.reddit.com/r/PLC/comments/gqzyem/psa_twincat_3_hyperv_wsl_2_working_using_vmware/">Reddit post</a> which mentioned VMware works with TwinCAT 3 and Hyper-V. This provided me with a nice opportunity to test Tc/BSD because I didn’t want to create a big Windows VM. Also it allowed me to answer a <a href="https://stackoverflow.com/questions/71321786/how-can-i-use-a-local-twincat-3-runtime-with-hyper-v-enabled/71333438#71333438">StackOverflow question</a> 🥳. Furthermore, I found out that it also works with Virtual Box.</p>

<h2 id="installing-tcbsd-on-virtualbox">Installing Tc/BSD on VirtualBox</h2>

<p>Installing Tc/BSD on Virtual Box is quite straightforward, thanks to <a href="https://github.com/PTKu/TwinCAT-BSD-VM-creator">an install script</a> from the community. Furthermore, there is also a <a href="https://beckhoff-au.teachable.com/courses/1204788/lectures/37382778">video by Coding Bytes</a> describing installation.</p>

<p>To install Tc/BSD:</p>

<ol>
  <li><a href="https://www.virtualbox.org/wiki/Downloads">Download</a> and install Virtual Box.</li>
  <li><a href="https://github.com/PTKu/TwinCAT-BSD-VM-creator">Download or clone</a> the install script.</li>
  <li>
    <p>Go to the <a href="https://www.beckhoff.com/en-us/search-results/?q=bsd">Beckhoff website</a> and download the TwinCAT/BSD ISO.</p>

    <p><a href="/assets/2022-tcbsd/download_tcbsd_iso.png"><picture><source srcset="/generated/2022-tcbsd/download_tcbsd_iso-300-05028dc83.webp 300w, /generated/2022-tcbsd/download_tcbsd_iso-500-05028dc83.webp 500w, /generated/2022-tcbsd/download_tcbsd_iso-615-05028dc83.webp 615w" type="image/webp"><source srcset="/generated/2022-tcbsd/download_tcbsd_iso-300-151822c24.png 300w, /generated/2022-tcbsd/download_tcbsd_iso-500-151822c24.png 500w, /generated/2022-tcbsd/download_tcbsd_iso-615-151822c24.png 615w" type="image/png"><img src="/generated/2022-tcbsd/download_tcbsd_iso-615-151822c24.png" width="615" height="151"></picture></a></p>
  </li>
  <li>Extract the contents of the installer script and the Tc/BSD ISO. Copy the contents of both to a single folder.</li>
  <li>Copy the exact filename of the Tc/BSD ISO image and open <code class="language-plaintext highlighter-rouge">Create-TcBsdVM.ps1</code> or <code class="language-plaintext highlighter-rouge">TwinCAT BSD VM creator.bat</code> with a text editor, depending on which you want to use.</li>
  <li>Change the ISO filename in one of the installer scripts to the one which matches your downloaded ISO file. Here <code class="language-plaintext highlighter-rouge">"TCBSD-x64-13-55702.iso"</code> is the name of the ISO file I downloaded.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Create-TcBsdVM.ps1</code> at line 7: <code class="language-plaintext highlighter-rouge">$tcbsdimagefile="TCBSD-x64-13-55702.iso",</code></li>
      <li><code class="language-plaintext highlighter-rouge">TwinCAT BSD VM creator.bat</code> at line 2: <code class="language-plaintext highlighter-rouge">SET sourcefilename="TCBSD-x64-13-55702.iso"</code></li>
    </ul>
  </li>
  <li>Save and close the installer script.</li>
  <li>Then either double click <code class="language-plaintext highlighter-rouge">TwinCAT BSD VM creator.bat</code> to execute, or from powershell run <code class="language-plaintext highlighter-rouge">Create-TcBsdVM.ps1 TcBSD-VM</code>. Here the first argument, <code class="language-plaintext highlighter-rouge">TcBSD-VM</code>, is the name of the virtual machine.</li>
  <li>
    <p>You should see a Virtual Box window open and then you wait for the installer to start. This can take a minute. After that, you should see the following screen. Select <strong>TC/BSD Install</strong> and hit enter.</p>

    <p><a href="/assets/2022-tcbsd/bsd_install.png"><picture><source srcset="/generated/2022-tcbsd/bsd_install-300-56c487f3a.webp 300w, /generated/2022-tcbsd/bsd_install-500-56c487f3a.webp 500w, /generated/2022-tcbsd/bsd_install-720-56c487f3a.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/bsd_install-300-480bc4212.png 300w, /generated/2022-tcbsd/bsd_install-500-480bc4212.png 500w, /generated/2022-tcbsd/bsd_install-720-480bc4212.png 720w" type="image/png"><img src="/generated/2022-tcbsd/bsd_install-800-480bc4212.png" width="1026" height="825"></picture></a></p>
  </li>
  <li>Then select the 4G hard drive which was automatically created by the script and hit enter.</li>
  <li>
    <p>Hit enter again to acknowledge the warning.</p>

    <p><a href="/assets/2022-tcbsd/warning.png"><picture><source srcset="/generated/2022-tcbsd/warning-300-0c6d19cb1.webp 300w, /generated/2022-tcbsd/warning-500-0c6d19cb1.webp 500w, /generated/2022-tcbsd/warning-568-0c6d19cb1.webp 568w" type="image/webp"><source srcset="/generated/2022-tcbsd/warning-300-1fae5fe10.png 300w, /generated/2022-tcbsd/warning-500-1fae5fe10.png 500w, /generated/2022-tcbsd/warning-568-1fae5fe10.png 568w" type="image/png"><img src="/generated/2022-tcbsd/warning-568-1fae5fe10.png" width="568" height="224"></picture></a></p>
  </li>
  <li>Then it asks for you to generate a password. Type in a password and repeat it to make sure it’s correct.</li>
  <li>
    <p>After several minutes the installation is complete and you see the following screen. Select <strong>OK</strong>.</p>

    <p><a href="/assets/2022-tcbsd/complete.png"><picture><source srcset="/generated/2022-tcbsd/complete-264-015ad2c81.webp 264w" type="image/webp"><source srcset="/generated/2022-tcbsd/complete-264-7e4adb64f.png 264w" type="image/png"><img src="/generated/2022-tcbsd/complete-264-7e4adb64f.png" width="264" height="215"></picture></a></p>
  </li>
  <li>
    <p>In the new menu select <strong>Shutdown</strong>.</p>

    <p><a href="/assets/2022-tcbsd/reboot.png"><picture><source srcset="/generated/2022-tcbsd/reboot-300-7a42d66c4.webp 300w, /generated/2022-tcbsd/reboot-406-7a42d66c4.webp 406w" type="image/webp"><source srcset="/generated/2022-tcbsd/reboot-300-70f095f8a.png 300w, /generated/2022-tcbsd/reboot-406-70f095f8a.png 406w" type="image/png"><img src="/generated/2022-tcbsd/reboot-406-70f095f8a.png" width="406" height="436"></picture></a></p>
  </li>
  <li>
    <p>Open the <strong>Settings &gt; Network</strong> of the virtual machine you created. Now there are different options for different use cases:</p>

    <ul>
      <li>
        <p>If you just use the runtime locally, enable a Network adapter 1 and set it as a <strong>Host only adapter</strong></p>

        <p><a href="/assets/2022-tcbsd/virtual_box_host_only_adapter.png"><picture><source srcset="/generated/2022-tcbsd/virtual_box_host_only_adapter-300-14abd6629.webp 300w, /generated/2022-tcbsd/virtual_box_host_only_adapter-500-14abd6629.webp 500w, /generated/2022-tcbsd/virtual_box_host_only_adapter-720-14abd6629.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/virtual_box_host_only_adapter-300-86242b29e.png 300w, /generated/2022-tcbsd/virtual_box_host_only_adapter-500-86242b29e.png 500w, /generated/2022-tcbsd/virtual_box_host_only_adapter-720-86242b29e.png 720w" type="image/png"><img src="/generated/2022-tcbsd/virtual_box_host_only_adapter-800-86242b29e.png" width="1298" height="1036"></picture></a></p>
      </li>
      <li>
        <p>If you want to set up a physical connection to an outside network select <strong>Bridged adapter</strong></p>

        <p><a href="/assets/2022-tcbsd/virtual_box_bridged_adapter.png"><picture><source srcset="/generated/2022-tcbsd/virtual_box_bridged_adapter-300-dcdc15b80.webp 300w, /generated/2022-tcbsd/virtual_box_bridged_adapter-500-dcdc15b80.webp 500w, /generated/2022-tcbsd/virtual_box_bridged_adapter-720-dcdc15b80.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/virtual_box_bridged_adapter-300-ba2e8aa94.png 300w, /generated/2022-tcbsd/virtual_box_bridged_adapter-500-ba2e8aa94.png 500w, /generated/2022-tcbsd/virtual_box_bridged_adapter-720-ba2e8aa94.png 720w" type="image/png"><img src="/generated/2022-tcbsd/virtual_box_bridged_adapter-800-ba2e8aa94.png" width="1298" height="1036"></picture></a></p>
      </li>
      <li>
        <p>If you want the Tc/BSD package manager to work, you need to enable both. For adapter 1, select <strong>Host only adapter</strong> and enable a second adapter where you select <strong>Bridged adapter</strong>.</p>
      </li>
    </ul>
  </li>
  <li>
    <p>Verify that the network connections work by starting the virtual machine, logging in and:</p>

    <ul>
      <li>
        <p>Run <code class="language-plaintext highlighter-rouge">ifconfig</code> . You should see an <code class="language-plaintext highlighter-rouge">inet</code> starting with <code class="language-plaintext highlighter-rouge">192.168.</code>.
<a href="/assets/2022-tcbsd/ifconfig_virtual_box.png"><picture><source srcset="/generated/2022-tcbsd/ifconfig_virtual_box-300-0c4fb3b27.webp 300w, /generated/2022-tcbsd/ifconfig_virtual_box-500-0c4fb3b27.webp 500w, /generated/2022-tcbsd/ifconfig_virtual_box-720-0c4fb3b27.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/ifconfig_virtual_box-300-aaee1fc5c.png 300w, /generated/2022-tcbsd/ifconfig_virtual_box-500-aaee1fc5c.png 500w, /generated/2022-tcbsd/ifconfig_virtual_box-720-aaee1fc5c.png 720w" type="image/png"><img src="/generated/2022-tcbsd/ifconfig_virtual_box-800-aaee1fc5c.png" width="1770" height="991"></picture></a></p>
      </li>
      <li>
        <p>You can also try to <code class="language-plaintext highlighter-rouge">ping www.beckhoff.com</code> and see if you get a reply. For me, it took a minute before the network connection started to work.</p>
      </li>
    </ul>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	$ ping www.beckhoff.com
	PING d2la171hyf8xwx.cloudfront.net (52.222.139.54): 56 data bytes
	64 bytes from 52.222.139.54: icmp_seq=0 ttl=128 time=9.673 ms
	64 bytes from 52.222.139.54: icmp_seq=1 ttl=128 time=8.739 ms
	64 bytes from 52.222.139.54: icmp_seq=2 ttl=128 time=9.783 ms
	^C
	--- d2la171hyf8xwx.cloudfront.net ping statistics ---
	3 packets transmitted, 3 packets received, 0.0% packet loss
</code></pre></div></div>

<ul>
  <li>In case it is not working, check your network adapter settings. Type <strong>Ethernet settings</strong> into the start menu and open it. Select <strong>Change adapter options</strong> and right-click <strong>VirtualBox Host-Only Network</strong> and select <strong>Properties</strong>. Make sure they are set to automatic.
<a href="/assets/2022-tcbsd/ipv4_settings.png"><picture><source srcset="/generated/2022-tcbsd/ipv4_settings-300-5e484ad72.webp 300w, /generated/2022-tcbsd/ipv4_settings-500-5e484ad72.webp 500w, /generated/2022-tcbsd/ipv4_settings-720-5e484ad72.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/ipv4_settings-300-be31c9f34.png 300w, /generated/2022-tcbsd/ipv4_settings-500-be31c9f34.png 500w, /generated/2022-tcbsd/ipv4_settings-720-be31c9f34.png 720w" type="image/png"><img src="/generated/2022-tcbsd/ipv4_settings-800-be31c9f34.png" width="1566" height="979"></picture></a></li>
</ul>

<h2 id="installing-tcbsd-on-vmware">Installing Tc/BSD on VMware</h2>

<p>Installing TwinCAT/BSD on VMware is a bit more complicated. The steps below are based on a <a href="https://www.youtube.com/watch?v=H-qfWfz37Fg">YouTube tutorial from Jakob Sagatowski</a>. Before you head over there, I did notice that I had to do several things differently for it to work.</p>

<h3 id="create-a-bootable-usb">Create a bootable USB</h3>

<ol>
  <li>
    <p>Install Rufus to create a bootable USB drive.</p>

    <ul>
      <li>Install via the terminal <code class="language-plaintext highlighter-rouge">winget install -e --id Rufus.Rufus</code>.</li>
      <li>Or <a href="https://rufus.ie/">download manually</a> and install. You can also use the portable version, which doesn’t require any installation.</li>
    </ul>
  </li>
  <li>
    <p>Go to the <a href="https://www.beckhoff.com/en-us/search-results/?q=bsd">Beckhoff website</a> and download the TwinCAT/BSD ISO.</p>

    <p><a href="/assets/2022-tcbsd/download_tcbsd_iso.png"><picture><source srcset="/generated/2022-tcbsd/download_tcbsd_iso-300-05028dc83.webp 300w, /generated/2022-tcbsd/download_tcbsd_iso-500-05028dc83.webp 500w, /generated/2022-tcbsd/download_tcbsd_iso-615-05028dc83.webp 615w" type="image/webp"><source srcset="/generated/2022-tcbsd/download_tcbsd_iso-300-151822c24.png 300w, /generated/2022-tcbsd/download_tcbsd_iso-500-151822c24.png 500w, /generated/2022-tcbsd/download_tcbsd_iso-615-151822c24.png 615w" type="image/png"><img src="/generated/2022-tcbsd/download_tcbsd_iso-615-151822c24.png" width="615" height="151"></picture></a></p>
  </li>
  <li>
    <p>Unzip the file you just downloaded. You’ll probably find three files in there. The ISO one is the one you need.</p>
  </li>
  <li>
    <p>Get yourself a USB drive which doesn’t contain important information, since it’s formatted in the process. I used an old 1 GB USB drive that I bought for €30 a long time ago 😱.</p>
  </li>
  <li>
    <p>Open Rufus, click <strong>SELECT</strong> and select the ISO file you just unpacked.</p>

    <p><a href="/assets/2022-tcbsd/rufus_select_iso.png"><picture><source srcset="/generated/2022-tcbsd/rufus_select_iso-300-1bedc024a.webp 300w, /generated/2022-tcbsd/rufus_select_iso-474-1bedc024a.webp 474w" type="image/webp"><source srcset="/generated/2022-tcbsd/rufus_select_iso-300-6b5e79461.png 300w, /generated/2022-tcbsd/rufus_select_iso-474-6b5e79461.png 474w" type="image/png"><img src="/generated/2022-tcbsd/rufus_select_iso-474-6b5e79461.png" width="474" height="538"></picture></a></p>
  </li>
  <li>
    <p>Then select <strong>START</strong>. It then formats the USB drive and makes it a bootable USB drive with Tc/BSD on it.</p>
  </li>
</ol>

<h3 id="installing-twincatbsd">Installing TwinCAT/BSD</h3>

<ol>
  <li>
    <p>Download and install VMware Workstation Player. It’s free for non-commercial use.</p>

    <ul>
      <li>Install from the terminal with <code class="language-plaintext highlighter-rouge">winget install -e --id VMware.WorkstationPlayer</code></li>
      <li>Or <a href="https://www.vmware.com/products/workstation-player/workstation-player-evaluation.html">manually download</a> and install.</li>
    </ul>
  </li>
  <li>
    <p>Select the free license or enter a license number</p>
  </li>
  <li>
    <p>Start VMware Workstation Player with Administrator rights and select create a New Virtual Machine. You need administrator rights later to access the USB drive.</p>

    <p><a href="/assets/2022-tcbsd/create_new_vm.png"><picture><source srcset="/generated/2022-tcbsd/create_new_vm-300-1c2480a24.webp 300w, /generated/2022-tcbsd/create_new_vm-500-1c2480a24.webp 500w, /generated/2022-tcbsd/create_new_vm-714-1c2480a24.webp 714w" type="image/webp"><source srcset="/generated/2022-tcbsd/create_new_vm-300-9b570045c.png 300w, /generated/2022-tcbsd/create_new_vm-500-9b570045c.png 500w, /generated/2022-tcbsd/create_new_vm-714-9b570045c.png 714w" type="image/png"><img src="/generated/2022-tcbsd/create_new_vm-714-9b570045c.png" width="714" height="588"></picture></a></p>
  </li>
  <li>
    <p>Select <strong>I will install the operating system later</strong>.</p>

    <p><a href="/assets/2022-tcbsd/install_os_later.png"><picture><source srcset="/generated/2022-tcbsd/install_os_later-300-34722453c.webp 300w, /generated/2022-tcbsd/install_os_later-500-34722453c.webp 500w, /generated/2022-tcbsd/install_os_later-641-34722453c.webp 641w" type="image/webp"><source srcset="/generated/2022-tcbsd/install_os_later-300-4d68f69d2.png 300w, /generated/2022-tcbsd/install_os_later-500-4d68f69d2.png 500w, /generated/2022-tcbsd/install_os_later-641-4d68f69d2.png 641w" type="image/png"><img src="/generated/2022-tcbsd/install_os_later-641-4d68f69d2.png" width="641" height="630"></picture></a></p>
  </li>
  <li>
    <p>Select “Other” and “FreeBSD 12 64-bit”</p>

    <p><a href="/assets/2022-tcbsd/select_guest_os.png"><picture><source srcset="/generated/2022-tcbsd/select_guest_os-300-d075c4030.webp 300w, /generated/2022-tcbsd/select_guest_os-428-d075c4030.webp 428w" type="image/webp"><source srcset="/generated/2022-tcbsd/select_guest_os-300-38be0acf5.png 300w, /generated/2022-tcbsd/select_guest_os-428-38be0acf5.png 428w" type="image/png"><img src="/generated/2022-tcbsd/select_guest_os-428-38be0acf5.png" width="428" height="430"></picture></a></p>
  </li>
  <li>
    <p>Give your virtual machine a descriptive name and change the save location if you would like to.</p>

    <p><a href="/assets/2022-tcbsd/name_vm.png"><picture><source srcset="/generated/2022-tcbsd/name_vm-300-ba1f3fb9f.webp 300w, /generated/2022-tcbsd/name_vm-428-ba1f3fb9f.webp 428w" type="image/webp"><source srcset="/generated/2022-tcbsd/name_vm-300-0f6b157b6.png 300w, /generated/2022-tcbsd/name_vm-428-0f6b157b6.png 428w" type="image/png"><img src="/generated/2022-tcbsd/name_vm-428-0f6b157b6.png" width="428" height="430"></picture></a></p>
  </li>
  <li>
    <p>Specify the disk capacity. I left it at the default 20 GB. Note: <a href="https://www.youtube.com/watch?v=H-qfWfz37Fg">Jakob</a> removes this hard drive and adds the USB drive first and then adds a new hard drive. But, I found that if I do this, I get an error<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> once I remove the USB drive.</p>

    <p><a href="/assets/2022-tcbsd/disk_vm.png"><picture><source srcset="/generated/2022-tcbsd/disk_vm-300-1d56d175c.webp 300w, /generated/2022-tcbsd/disk_vm-428-1d56d175c.webp 428w" type="image/webp"><source srcset="/generated/2022-tcbsd/disk_vm-300-054149f33.png 300w, /generated/2022-tcbsd/disk_vm-428-054149f33.png 428w" type="image/png"><img src="/generated/2022-tcbsd/disk_vm-428-054149f33.png" width="428" height="430"></picture></a></p>
  </li>
  <li>
    <p>You see a screen summarizing the results. Click <strong>Finish</strong>.</p>
  </li>
  <li>
    <p>Select the virtual machine you just created and select <strong>Edit virtual Machine Settings</strong>.</p>

    <p><a href="/assets/2022-tcbsd/edit_vm_settings.png"><picture><source srcset="/generated/2022-tcbsd/edit_vm_settings-300-67e78f7ae.webp 300w, /generated/2022-tcbsd/edit_vm_settings-500-67e78f7ae.webp 500w, /generated/2022-tcbsd/edit_vm_settings-714-67e78f7ae.webp 714w" type="image/webp"><source srcset="/generated/2022-tcbsd/edit_vm_settings-300-aa3c043a8.png 300w, /generated/2022-tcbsd/edit_vm_settings-500-aa3c043a8.png 500w, /generated/2022-tcbsd/edit_vm_settings-714-aa3c043a8.png 714w" type="image/png"><img src="/generated/2022-tcbsd/edit_vm_settings-714-aa3c043a8.png" width="714" height="588"></picture></a></p>
  </li>
  <li>
    <p>Increase the memory of the virtual machine. I set it to 1 GB. At 256 MB the virtual machine sometimes gets killed when it runs out of memory. If you notice that it crashes, you know what to do.</p>

    <p><a href="/assets/2022-tcbsd/vm_memory.png"><picture><source srcset="/generated/2022-tcbsd/vm_memory-300-cd4d8be82.webp 300w, /generated/2022-tcbsd/vm_memory-500-cd4d8be82.webp 500w, /generated/2022-tcbsd/vm_memory-720-cd4d8be82.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/vm_memory-300-714f91e76.png 300w, /generated/2022-tcbsd/vm_memory-500-714f91e76.png 500w, /generated/2022-tcbsd/vm_memory-720-714f91e76.png 720w" type="image/png"><img src="/generated/2022-tcbsd/vm_memory-750-714f91e76.png" width="750" height="734"></picture></a></p>
  </li>
  <li>
    <p>Add the USB drive as a hard disk by clicking on <strong>Add</strong>.</p>

    <p><a href="/assets/2022-tcbsd/replace_harddisk.png"><picture><source srcset="/generated/2022-tcbsd/replace_harddisk-300-e89aa245a.webp 300w, /generated/2022-tcbsd/replace_harddisk-500-e89aa245a.webp 500w, /generated/2022-tcbsd/replace_harddisk-720-e89aa245a.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/replace_harddisk-300-79b093bb7.png 300w, /generated/2022-tcbsd/replace_harddisk-500-79b093bb7.png 500w, /generated/2022-tcbsd/replace_harddisk-720-79b093bb7.png 720w" type="image/png"><img src="/generated/2022-tcbsd/replace_harddisk-750-79b093bb7.png" width="750" height="734"></picture></a></p>
  </li>
  <li>Then in the new window, select <strong>Hard Disk</strong> and click <strong>Next</strong>.</li>
  <li>Select <strong>SATA</strong> and click <strong>Next</strong>.</li>
  <li>Select <strong>Use a physical disk (for advanced users)</strong>. Yes, that’s you :D. Click on <strong>Next</strong>.</li>
  <li>
    <p>Now you get prompted to choose a disk. Here you want to select your USB drive, which should still be on your computer.</p>

    <p><a href="/assets/2022-tcbsd/select_disk.png"><picture><source srcset="/generated/2022-tcbsd/select_disk-300-833c70fa0.webp 300w, /generated/2022-tcbsd/select_disk-444-833c70fa0.webp 444w" type="image/webp"><source srcset="/generated/2022-tcbsd/select_disk-300-59914ac6b.png 300w, /generated/2022-tcbsd/select_disk-444-59914ac6b.png 444w" type="image/png"><img src="/generated/2022-tcbsd/select_disk-444-59914ac6b.png" width="444" height="430"></picture></a></p>
  </li>
  <li>
    <p>To find out if you need to select Disk 0 or Disk 1. Type in <code class="language-plaintext highlighter-rouge">diskmgmt</code> in the start menu to open <strong>Create and format hard disk partitions</strong>.</p>

    <p><a href="/assets/2022-tcbsd/diskmgmt.png"><picture><source srcset="/generated/2022-tcbsd/diskmgmt-300-1eb1cec2f.webp 300w, /generated/2022-tcbsd/diskmgmt-500-1eb1cec2f.webp 500w, /generated/2022-tcbsd/diskmgmt-720-1eb1cec2f.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/diskmgmt-300-3c2b46a29.png 300w, /generated/2022-tcbsd/diskmgmt-500-3c2b46a29.png 500w, /generated/2022-tcbsd/diskmgmt-720-3c2b46a29.png 720w" type="image/png"><img src="/generated/2022-tcbsd/diskmgmt-784-3c2b46a29.png" width="784" height="680"></picture></a></p>
  </li>
  <li>
    <p>You see several partitions. In my case, there are two. Disk 1 is the removable one, thus I want to use that as the hard drive.</p>

    <p><a href="/assets/2022-tcbsd/disk0_disk1.png"><picture><source srcset="/generated/2022-tcbsd/disk0_disk1-300-53bfc229f.webp 300w, /generated/2022-tcbsd/disk0_disk1-500-53bfc229f.webp 500w, /generated/2022-tcbsd/disk0_disk1-720-53bfc229f.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/disk0_disk1-300-aa7f30b63.png 300w, /generated/2022-tcbsd/disk0_disk1-500-aa7f30b63.png 500w, /generated/2022-tcbsd/disk0_disk1-720-aa7f30b63.png 720w" type="image/png"><img src="/generated/2022-tcbsd/disk0_disk1-731-aa7f30b63.png" width="731" height="200"></picture></a></p>
  </li>
  <li>
    <p>Now go back to the virtual machine settings and select <strong>PhysicalDrive1</strong>, <strong>Next</strong> and <strong>Finish</strong>.</p>

    <p><a href="/assets/2022-tcbsd/select_disk1.png"><picture><source srcset="/generated/2022-tcbsd/select_disk1-300-082cded48.webp 300w, /generated/2022-tcbsd/select_disk1-444-082cded48.webp 444w" type="image/webp"><source srcset="/generated/2022-tcbsd/select_disk1-300-89890f090.png 300w, /generated/2022-tcbsd/select_disk1-444-89890f090.png 444w" type="image/png"><img src="/generated/2022-tcbsd/select_disk1-444-89890f090.png" width="444" height="430"></picture></a></p>
  </li>
  <li>
    <p>Finally, you set the firmware type of the USB drive to <strong>UEFI</strong>. If you have a Pro version of VMware, you can set the firmware type in the virtual machine settings under <strong>Options &gt; Advanced &gt; Firmware type</strong> and select <strong>UEFI</strong>. If you have <a href="https://stackoverflow.com/a/71333438/6329629">the Player version</a>, you:</p>

    <ol>
      <li>
        <p>Locate the directory of the virtual machine. You can find its location under <strong>Edit virtual machine settings &gt; Hard Disk (SCSI)</strong></p>

        <p><a href="/assets/2022-tcbsd/vm_location.png"><picture><source srcset="/generated/2022-tcbsd/vm_location-300-bf50faa2a.webp 300w, /generated/2022-tcbsd/vm_location-500-bf50faa2a.webp 500w, /generated/2022-tcbsd/vm_location-720-bf50faa2a.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/vm_location-300-cd9d7072f.png 300w, /generated/2022-tcbsd/vm_location-500-cd9d7072f.png 500w, /generated/2022-tcbsd/vm_location-720-cd9d7072f.png 720w" type="image/png"><img src="/generated/2022-tcbsd/vm_location-800-cd9d7072f.png" width="1086" height="180"></picture></a></p>
      </li>
      <li>Open the VMX file in the virtual machine directory with a text editor.</li>
      <li>Find the line <code class="language-plaintext highlighter-rouge">firmware = "bios"</code> and replace with <code class="language-plaintext highlighter-rouge">firmware = "efi"</code> and save it. If this line doesn’t exist, add it somewhere.</li>
    </ol>

    <p><a href="/assets/2022-tcbsd/player_vs_pro.png"><picture><source srcset="/generated/2022-tcbsd/player_vs_pro-300-9fc30b104.webp 300w, /generated/2022-tcbsd/player_vs_pro-500-9fc30b104.webp 500w, /generated/2022-tcbsd/player_vs_pro-720-9fc30b104.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/player_vs_pro-300-552889640.png 300w, /generated/2022-tcbsd/player_vs_pro-500-552889640.png 500w, /generated/2022-tcbsd/player_vs_pro-720-552889640.png 720w" type="image/png"><img src="/generated/2022-tcbsd/player_vs_pro-800-552889640.png" width="1500" height="734"></picture></a></p>
  </li>
  <li>
    <p>Now start the virtual machine with <strong>Play virtual machine</strong>.</p>
  </li>
  <li>
    <p>Wait for the installer to start up. This can take a minute. After that, you see the following screen. Select <strong>TC/BSD Install</strong> and hit enter.</p>

    <p><a href="/assets/2022-tcbsd/bsd_install.png"><picture><source srcset="/generated/2022-tcbsd/bsd_install-300-56c487f3a.webp 300w, /generated/2022-tcbsd/bsd_install-500-56c487f3a.webp 500w, /generated/2022-tcbsd/bsd_install-720-56c487f3a.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/bsd_install-300-480bc4212.png 300w, /generated/2022-tcbsd/bsd_install-500-480bc4212.png 500w, /generated/2022-tcbsd/bsd_install-720-480bc4212.png 720w" type="image/png"><img src="/generated/2022-tcbsd/bsd_install-800-480bc4212.png" width="1026" height="825"></picture></a></p>
  </li>
  <li>
    <p>Then select the 20G virtual hard drive you made and hit enter.</p>

    <p><a href="/assets/2022-tcbsd/select_harddrive.png"><picture><source srcset="/generated/2022-tcbsd/select_harddrive-300-6e6c1ecbb.webp 300w, /generated/2022-tcbsd/select_harddrive-500-6e6c1ecbb.webp 500w, /generated/2022-tcbsd/select_harddrive-624-6e6c1ecbb.webp 624w" type="image/webp"><source srcset="/generated/2022-tcbsd/select_harddrive-300-09182c2d4.png 300w, /generated/2022-tcbsd/select_harddrive-500-09182c2d4.png 500w, /generated/2022-tcbsd/select_harddrive-624-09182c2d4.png 624w" type="image/png"><img src="/generated/2022-tcbsd/select_harddrive-624-09182c2d4.png" width="624" height="384"></picture></a></p>
  </li>
  <li>
    <p>Hit enter again to acknowledge the warning.</p>

    <p><a href="/assets/2022-tcbsd/warning.png"><picture><source srcset="/generated/2022-tcbsd/warning-300-0c6d19cb1.webp 300w, /generated/2022-tcbsd/warning-500-0c6d19cb1.webp 500w, /generated/2022-tcbsd/warning-568-0c6d19cb1.webp 568w" type="image/webp"><source srcset="/generated/2022-tcbsd/warning-300-1fae5fe10.png 300w, /generated/2022-tcbsd/warning-500-1fae5fe10.png 500w, /generated/2022-tcbsd/warning-568-1fae5fe10.png 568w" type="image/png"><img src="/generated/2022-tcbsd/warning-568-1fae5fe10.png" width="568" height="224"></picture></a></p>
  </li>
  <li>Then it asks for you to generate a password. Type in a password and repeat it to make sure it’s correct.</li>
  <li>
    <p>After several minutes the installation is complete and you see the following screen. Select <strong>OK</strong>.</p>

    <p><a href="/assets/2022-tcbsd/complete.png"><picture><source srcset="/generated/2022-tcbsd/complete-264-015ad2c81.webp 264w" type="image/webp"><source srcset="/generated/2022-tcbsd/complete-264-7e4adb64f.png 264w" type="image/png"><img src="/generated/2022-tcbsd/complete-264-7e4adb64f.png" width="264" height="215"></picture></a></p>
  </li>
  <li>
    <p>In the new menu select <strong>Shutdown</strong> so that you can remove the USB drive.</p>

    <p><a href="/assets/2022-tcbsd/reboot.png"><picture><source srcset="/generated/2022-tcbsd/reboot-300-7a42d66c4.webp 300w, /generated/2022-tcbsd/reboot-406-7a42d66c4.webp 406w" type="image/webp"><source srcset="/generated/2022-tcbsd/reboot-300-70f095f8a.png 300w, /generated/2022-tcbsd/reboot-406-70f095f8a.png 406w" type="image/png"><img src="/generated/2022-tcbsd/reboot-406-70f095f8a.png" width="406" height="436"></picture></a></p>
  </li>
  <li>Remove USB drive under <strong>Edit virtual machine settings</strong>. Make sure you don’t select the 20 GB hard drive, on which you just installed Tc/BSD.</li>
  <li>Go back to the main menu and start the virtual machine.</li>
  <li>
    <p>After some time you see the login screen. Log in with your user name and password you made during the installation.</p>

    <p><a href="/assets/2022-tcbsd/bsd_login.png"><picture><source srcset="/generated/2022-tcbsd/bsd_login-300-ba04eb437.webp 300w, /generated/2022-tcbsd/bsd_login-500-ba04eb437.webp 500w, /generated/2022-tcbsd/bsd_login-720-ba04eb437.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/bsd_login-300-d1c8934de.png 300w, /generated/2022-tcbsd/bsd_login-500-d1c8934de.png 500w, /generated/2022-tcbsd/bsd_login-720-d1c8934de.png 720w" type="image/png"><img src="/generated/2022-tcbsd/bsd_login-800-d1c8934de.png" width="1053" height="934"></picture></a></p>
  </li>
  <li>Congrats. You now have a running version of TwinCAT/BSD.</li>
</ol>

<h2 id="device-manager">Device manager</h2>

<p>As I mentioned earlier, Tc/BSD doesn’t have a desktop environment. Most things are done from the terminal. However, the device manager has a GUI. To access it, you first need to find out the IP of the virtual machine. You find the IP by typing in <code class="language-plaintext highlighter-rouge">ifconfig</code>. The IP is right after <code class="language-plaintext highlighter-rouge">inet</code>.</p>

<p><a href="/assets/2022-tcbsd/ifconfig.png"><picture><source srcset="/generated/2022-tcbsd/ifconfig-300-9a05f4b63.webp 300w, /generated/2022-tcbsd/ifconfig-500-9a05f4b63.webp 500w, /generated/2022-tcbsd/ifconfig-720-9a05f4b63.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/ifconfig-300-2d9ab3652.png 300w, /generated/2022-tcbsd/ifconfig-500-2d9ab3652.png 500w, /generated/2022-tcbsd/ifconfig-720-2d9ab3652.png 720w" type="image/png"><img src="/generated/2022-tcbsd/ifconfig-800-2d9ab3652.png" width="1022" height="411"></picture></a></p>

<p>Then open your browser and type in the IP address you found. For me, it’s <code class="language-plaintext highlighter-rouge">https://192.168.126.128</code>. It’s possible that a warning shows up. Accept the risk and continue.</p>

<p><a href="/assets/2022-tcbsd/firefox_accept_risk.png"><picture><source srcset="/generated/2022-tcbsd/firefox_accept_risk-300-25e7fbccd.webp 300w, /generated/2022-tcbsd/firefox_accept_risk-500-25e7fbccd.webp 500w, /generated/2022-tcbsd/firefox_accept_risk-720-25e7fbccd.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/firefox_accept_risk-300-8a4a732ec.png 300w, /generated/2022-tcbsd/firefox_accept_risk-500-8a4a732ec.png 500w, /generated/2022-tcbsd/firefox_accept_risk-720-8a4a732ec.png 720w" type="image/png"><img src="/generated/2022-tcbsd/firefox_accept_risk-800-8a4a732ec.png" width="1756" height="1149"></picture></a></p>

<p>From here you can log into the device manager with the username <code class="language-plaintext highlighter-rouge">Administrator</code> and the password you set during the installation. The device manager shows all kinds of information about the state of the hardware and the software.</p>

<p><a href="/assets/2022-tcbsd/device_manager.png"><picture><source srcset="/generated/2022-tcbsd/device_manager-300-c054b533e.webp 300w, /generated/2022-tcbsd/device_manager-500-c054b533e.webp 500w, /generated/2022-tcbsd/device_manager-720-c054b533e.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/device_manager-300-794770804.png 300w, /generated/2022-tcbsd/device_manager-500-794770804.png 500w, /generated/2022-tcbsd/device_manager-720-794770804.png 720w" type="image/png"><img src="/generated/2022-tcbsd/device_manager-800-794770804.png" width="883" height="393"></picture></a></p>

<h2 id="web-console">Web console</h2>

<p>Next to a device manager, the <code class="language-plaintext highlighter-rouge">https://192.168.126.128</code> page also has a link to a web console. The console shows the same as if you logged into the virtual machine directly, but the web version has a better interface. Mainly because it enables scrolling<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> and copy-pasting commands is easier.</p>

<h2 id="installing-packages">Installing packages</h2>

<p>As I mentioned earlier, there is no GUI. You do most actions from the terminal. One of the things you can do is install <a href="https://tcbsd.beckhoff.com/TCBSD/13/stable/packages/All/">packages</a>, with a package manager. Package managers are a useful feature in UNIX-like operating systems. Although Windows is now also joining the party with <a href="https://winget.run/"><code class="language-plaintext highlighter-rouge">winget</code></a>.</p>

<h3 id="installing-twincat-hmi-server">Installing TwinCAT HMI server</h3>

<p><em>Note: See also a <a href="https://beckhoff-au.teachable.com/courses/coding-bytes-twincat-3/lectures/37473242">video by Coding Bytes</a> which describes this step.</em></p>

<p>A package which you likely need is the HMI server. I don’t exactly know what it is called, but I know that there is a search feature. If you type in <code class="language-plaintext highlighter-rouge">pkg search hmi</code>, you see two results.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pkg search hmi
TF1810-PLC-HMI-Web-3.1.4024.11_1 TF1810 | TC3 PLC HMI Web
TF2000-HMI-Server-1.12.754.4   TF2000-HMI-Server
</code></pre></div></div>

<p>The first one is the old HMI and the second one is the one I want. To install it, type <code class="language-plaintext highlighter-rouge">doas pkg install TF2000-HMI-Server</code> and hit enter. Confirm the start of the installation with <code class="language-plaintext highlighter-rouge">y</code>. Then a warning showed up. So I did <code class="language-plaintext highlighter-rouge">doas service TcHmiSrv start</code> to start it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=====
Message from TF2000-HMI-Server-1.12.754.4:

--
============================== !!!! WARNING !!!! ==========================
FreeBSD package manager doesn't allow us to automatically start services
during package installation. To use the Beckhoff TwinCAT HMI Server either
restart your system or start 'TcHmiSrv' manually with:

doas service TcHmiSrv start

============================== !!!! WARNING !!!! ==========================
</code></pre></div></div>

<p>If you then type <code class="language-plaintext highlighter-rouge">top</code> and hit enter, you see TcHmiSrv is running. <code class="language-plaintext highlighter-rouge">top</code> is the task manager analogue of FreeBSD. It can be convenient to have it open to see if processes are running, or maybe if your virtual machine has crashed. To exit <code class="language-plaintext highlighter-rouge">top</code>, press <code class="language-plaintext highlighter-rouge">q</code> or <kbd>Ctrl</kbd> + <kbd>C</kbd>.</p>

<p><a href="/assets/2022-tcbsd/top_tchmi.png"><picture><source srcset="/generated/2022-tcbsd/top_tchmi-300-717070c03.webp 300w, /generated/2022-tcbsd/top_tchmi-500-717070c03.webp 500w, /generated/2022-tcbsd/top_tchmi-720-717070c03.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/top_tchmi-300-761251ad3.png 300w, /generated/2022-tcbsd/top_tchmi-500-761251ad3.png 500w, /generated/2022-tcbsd/top_tchmi-720-761251ad3.png 720w" type="image/png"><img src="/generated/2022-tcbsd/top_tchmi-800-761251ad3.png" width="1043" height="825"></picture></a></p>

<h2 id="running-plc-code">Running PLC code</h2>

<h3 id="connecting-to-the-virtual-machine">Connecting to the virtual machine</h3>

<p>Now you are ready to run some PLC and HMI code on Tc/BSD. You find the target machine the usual way. You go to <strong>Choose Target System &gt; Search (Ethernet) &gt; Broadcast search</strong>. Note that next to your regular Wi-Fi/Ethernet ports, also two ports from VMware or one from VirtualBox shows up. Make sure the ports of your virtual machine are selected and click <strong>OK</strong> to start the search.</p>

<p><a href="/assets/2022-tcbsd/select_ports.png"><picture><source srcset="/generated/2022-tcbsd/select_ports-300-43aad7664.webp 300w, /generated/2022-tcbsd/select_ports-500-43aad7664.webp 500w, /generated/2022-tcbsd/select_ports-541-43aad7664.webp 541w" type="image/webp"><source srcset="/generated/2022-tcbsd/select_ports-300-8daf8cda7.png 300w, /generated/2022-tcbsd/select_ports-500-8daf8cda7.png 500w, /generated/2022-tcbsd/select_ports-541-8daf8cda7.png 541w" type="image/png"><img src="/generated/2022-tcbsd/select_ports-541-8daf8cda7.png" width="541" height="217"></picture></a></p>

<p>Click <strong>Add Route</strong> select <strong>Secure ADS</strong> and click <strong>OK</strong>. In case it can’t connect, click <strong>Advanced</strong> to show the options. Then select <strong>IP address</strong> instead of <strong>Host name</strong>. Then try to connect again.</p>

<p><a href="/assets/2022-tcbsd/connected.png"><picture><source srcset="/generated/2022-tcbsd/connected-300-0938262fe.webp 300w, /generated/2022-tcbsd/connected-500-0938262fe.webp 500w, /generated/2022-tcbsd/connected-637-0938262fe.webp 637w" type="image/webp"><source srcset="/generated/2022-tcbsd/connected-300-f4b89fd79.png 300w, /generated/2022-tcbsd/connected-500-f4b89fd79.png 500w, /generated/2022-tcbsd/connected-637-f4b89fd79.png 637w" type="image/png"><img src="/generated/2022-tcbsd/connected-637-f4b89fd79.png" width="637" height="485"></picture></a></p>

<p>After connecting to the PLC, you should be able to activate your configuration. Finally. You can develop TwinCAT code again while <a href="https://alltwincat.com/2020/06/15/the-five-best-and-worst-things-with-twincat/#the-very-concept-of-twincat">sitting in a beer garden like some developers do</a>. Freedom!</p>

<h3 id="publishing-the-hmi">Publishing the HMI</h3>

<p>With your PLC code running, it’s now time to look at how to publish the HMI. I initially had some issues with publishing the HMI. In the end, I made it work by using TcBSD version 13, which has TcHmiSrv 1.12.756.1. I couldn’t make it work with TcBSD v12.2 which has TcHmiSrv v 1.12.746.0. Furthermore, you need to make sure that your HMI project has the same version as the server which is 1.12.756.1 for me.</p>

<p>Once you have that, you need to add an <a href="https://infosys.beckhoff.com/content/1033/twincat_bsd/6423780747.html?id=6393416005524011539">exception to the Tc/BSD firewall</a>. By default a lot of ports are blocked, including the one needed for the HMI server. To add it, open the firewall configuration file with <code class="language-plaintext highlighter-rouge">doas ee /etc/pf.conf</code> from the console. Then add the following new line at the end of the file</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># allow TcHmiSrv outgoing
pass in quick proto tcp to port 1010 keep state
</code></pre></div></div>

<p>Exit the <code class="language-plaintext highlighter-rouge">ee</code> editor with <kbd>Esc</kbd>, select <code class="language-plaintext highlighter-rouge">a) leave editor</code>, save your changes and restart the virtual machine.</p>

<p>Meanwhile, open the PLC project you want to publish on the virtual machine. Verify that the TF2000 HMI Server license is present in your system manager under <strong>SYSTEM &gt; License</strong>. If it’s not present, add it manually via the <strong>Manage Licenses</strong> tab. Then activate your PLC project which serves as the back end for your HMI project. Open the HMI publishing settings, fill in the IP address of the virtual machine and press <strong>Validate connection</strong>. If the connection was successful, select <strong>Publish</strong>.</p>

<p><a href="/assets/2022-tcbsd/VM_HMI_publish_settings.png"><picture><source srcset="/generated/2022-tcbsd/VM_HMI_publish_settings-300-fdfacc1af.webp 300w, /generated/2022-tcbsd/VM_HMI_publish_settings-500-fdfacc1af.webp 500w, /generated/2022-tcbsd/VM_HMI_publish_settings-720-fdfacc1af.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/VM_HMI_publish_settings-300-fbf801c3a.png 300w, /generated/2022-tcbsd/VM_HMI_publish_settings-500-fbf801c3a.png 500w, /generated/2022-tcbsd/VM_HMI_publish_settings-720-fbf801c3a.png 720w" type="image/png"><img src="/generated/2022-tcbsd/VM_HMI_publish_settings-800-fbf801c3a.png" width="955" height="831"></picture></a></p>

<p>If you connect to the HMI server for the first time, it asks you to set a password for the HMI server. You need this password whenever you want to publish your HMI project or need to log into the HMI server.</p>

<p><a href="/assets/2022-tcbsd/first_hmi_server_connection.png"><picture><source srcset="/generated/2022-tcbsd/first_hmi_server_connection-300-40af2a1dc.webp 300w, /generated/2022-tcbsd/first_hmi_server_connection-500-40af2a1dc.webp 500w, /generated/2022-tcbsd/first_hmi_server_connection-720-40af2a1dc.webp 720w" type="image/webp"><source srcset="/generated/2022-tcbsd/first_hmi_server_connection-300-b9986c121.png 300w, /generated/2022-tcbsd/first_hmi_server_connection-500-b9986c121.png 500w, /generated/2022-tcbsd/first_hmi_server_connection-720-b9986c121.png 720w" type="image/png"><img src="/generated/2022-tcbsd/first_hmi_server_connection-800-b9986c121.png" width="814" height="588"></picture></a></p>

<p>Once the password is set you can publish your HMI which then should be reachable at the Browser URL as shown in the publishing settings.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>The error message:</p>

      <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>No suitable dump device was found.
Setting hostuuid: 60e34d56-aa3b-ddb2-f508-cbe7cce89d64.
Setting hostid: 0x0789008c.
swapon: /dev/ada1p2: No such file or directory Starting file system checks: Can't open `/dev/ada1p1'
/dev/ada1p1: UNEXPECTED INCONSISTENCY; RUN fsck_msdosfs MANUALLY.
THE FOLLOWING FILE SYSTEM HAD AN UNEXPECTED INCONSISTENCY:
	msdosfs: /dev/ada1p1 (/boot/efi)
Automatic file system check failed; help!
ERROR: ABORTING BOOT (sending SIGTERM to parent)!
2022-06-04T08:26:05.670175+00:00 - init 1 - - /bin/sh on /etc/rc terminated abnormally, going to single user mode
Enter full pathname of shell or RETURN for /bin/sh:
root@:/
</code></pre></div>      </div>
      <p><a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">

      <p>While playing with Tc/BSD in the virtual machine I ran into an annoying issue: I couldn’t scroll up. For example, when I do <code class="language-plaintext highlighter-rouge">TcHmiSrv --help</code>, the help text is to long for the console screen. At first, I couldn’t figure out how to scroll back up, because there is no scroll bar, <kbd>↑</kbd> shows the previous command and <kbd>Page Up</kbd> just shows a <code class="language-plaintext highlighter-rouge">~</code>. After consulting Google, I found out that is where the <kbd>Scroll Lock</kbd> key is for. I always wondered what this button did. Note: If you’re on a laptop without a Scroll Lock key, you can <a href="https://serverfault.com/a/420341">remap one of the existing key combo’s</a>.
<img src="/assets/2022-tcbsd/scroll_lock_magic.gif" alt="scroll lock enables scrolling in the TcBSD terminal window" /> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[TwinCAT/BSD is a new operating system for the TwinCAT runtime. It has a small footprint and it’s free. Furthermore, TwinCAT/BSD allows you to run TwinCAT code locally when you have Hyper-V enabled if you install it on a virtual machine. In this tutorial, I go over the installation of TwinCAT/BSD on VMware and Virtual Box and show some basic usages of Tc/BSD.]]></summary></entry><entry><title type="html">No more formatting fights with pre-commits for TwinCAT</title><link href="https://cookncode.com/twincat/2022/04/14/pre-commit.html" rel="alternate" type="text/html" title="No more formatting fights with pre-commits for TwinCAT" /><published>2022-04-14T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/04/14/pre-commit</id><content type="html" xml:base="https://cookncode.com/twincat/2022/04/14/pre-commit.html"><![CDATA[<p><a href="https://cookncode.com/twincat/2021/06/07/tc-source-control-tips.html">Earlier</a> I talked about how you can do version control of your TwinCAT code with git. In this post, I want to show a neat feature of git which I didn’t mention last time: pre-commits. Pre-commits can format, lint, or do static code analyses on your code before committing. One pre-commit is available for structured text files. But, pre-commits are also available for markdown, HTML, or JavaScript files.</p>

<p><em>Use the <a href="https://github.com/rruiter87/TcTemplate">GitHub TwinCAT template repo</a> to set up a TwinCAT repo, including the pre-commits.</em></p>

<h2 id="what-are-pre-commits">What are pre-commits?</h2>

<p>Pre-commits are part of a class of so-called <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">git hooks</a>. Hooks enable you to run a script during a git command. Most of these hooks are pre-commit, meaning they do something before a commit, merge, or rebase. Pre-commit hooks can be beneficial; for instance, they can guarantee a uniform code formatting style or verify a file’s JSON or XML format.</p>

<p>You can see some examples of git hooks if you have a project which uses git. Navigate to the <code class="language-plaintext highlighter-rouge">.git/hooks</code> folder and you should see a list of example hooks there. If you can’t see the <code class="language-plaintext highlighter-rouge">.git</code> folder, make sure you have enabled “Hidden items” under the View tab in your Windows Explorer.</p>

<p><a href="/assets/2022-pre-commit/hook-examples.png"><picture><source srcset="/generated/2022-pre-commit/hook-examples-300-9aa9c0d95.webp 300w, /generated/2022-pre-commit/hook-examples-500-9aa9c0d95.webp 500w, /generated/2022-pre-commit/hook-examples-720-9aa9c0d95.webp 720w" type="image/webp"><source srcset="/generated/2022-pre-commit/hook-examples-300-ab0aa6599.png 300w, /generated/2022-pre-commit/hook-examples-500-ab0aa6599.png 500w, /generated/2022-pre-commit/hook-examples-720-ab0aa6599.png 720w" type="image/png"><img src="/generated/2022-pre-commit/hook-examples-800-ab0aa6599.png" alt="git hook examples as found in the ./git/hooks folder" width="1294" height="798"></picture></a></p>

<p>If you open one of the files, you see some bash scripts. It’s not necessary to use bash. You can use any programming or scripting language which is available on your system.</p>

<p>You can write your own hooks from scratch, but for most files, there is already <a href="https://pre-commit.com/hooks.html">a large variety available</a>. Let me show you how you can use these and a structured text hook for your TwinCAT projects.</p>

<h2 id="setup-pre-commit">Setup pre-commit</h2>

<p>A popular framework to manage pre-commits is <a href="https://pre-commit.com/">pre-commit</a>. It’s a Python based framework and thus you need a working Python installation on your system before you can use it.</p>

<ol>
  <li>
    <p>(In case you do not have Python) Download and install conda via one of the methods below. You can either install Miniconda which is a minimal installation. It comes with the bare necessities to get started. Miniconda should be enough for this tutorial. Or, if you would like to have a bit more tools/modules installed (mainly for data analyses), choose Anaconda.</p>

    <ul>
      <li>Manually download and install <a href="https://docs.conda.io/en/latest/miniconda.html">Miniconda</a> or <a href="https://www.anaconda.com/products/individual#Downloads">Anaconda</a>.</li>
      <li>Or run <code class="language-plaintext highlighter-rouge">winget install -e --id Anaconda.Miniconda3</code> or <code class="language-plaintext highlighter-rouge">winget install -e --id Anaconda.Anaconda3</code> from the terminal.</li>
    </ul>
  </li>
  <li>
    <p>Install pre-commit with either:</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">pip install pre-commit</code></li>
      <li>or <code class="language-plaintext highlighter-rouge">conda install -c conda-forge pre-commit</code></li>
    </ul>
  </li>
  <li>
    <p>Check if the installation went OK by running the command below in the terminal. If you see a version number then pre-commit works.</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    $ pre-commit --version
    pre-commit 2.17.0
</code></pre></div></div>

<ol start="4">
  <li>Next, create a new file called <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> in the git project folder (same folder as where your <code class="language-plaintext highlighter-rouge">.git</code> folder resides) where you want to start using pre-commits. Now you’re all set up to start using pre-commits.</li>
</ol>

<h2 id="twincat-relevant-pre-commits">TwinCAT relevant pre-commits</h2>

<p>The kind people of the Photon Controls and Data Systems at SLAC have open-sourced their TwinCAT pre-commits. To use the SLAC pre-commits, add the following lines to your <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> file:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">https://github.com/pcdshub/pre-commit-hooks</span>
  <span class="na">rev</span><span class="pi">:</span> <span class="s">v1.2.0</span>
  <span class="na">hooks</span><span class="pi">:</span>
    <span class="c1"># Replaces all leading tabs with spaces</span>
    <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">twincat-leading-tabs-remover</span>
    <span class="c1"># Removes line ids. See point 4 of the link for why you don't need them</span>
    <span class="c1"># https://cookncode.com/twincat/2021/06/07/tc-source-control-tips#2-creating-independent-files</span>
    <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">twincat-lineids-remover</span>
    <span class="c1"># Formats .tmc and .tpy files</span>
    <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">twincat-xml-format</span>
    <span class="c1"># Check if there are any libraries whose versions are not fixed</span>
    <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-fixed-library-versions</span>
</code></pre></div></div>

<p>For completeness, I’ll also add the following standard pre-commits. You can first run these pre-commits. Later <a href="#hooks-what-and-why">I go into details about what they do and why they are useful</a>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">repos</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">https://github.com/pre-commit/pre-commit-hooks</span>
    <span class="na">rev</span><span class="pi">:</span> <span class="s">v3.2.0</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="c1"># Removes trailing white spaces</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">trailing-whitespace</span>
      <span class="c1"># Checks yaml files for parseable syntax</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-yaml</span>
      <span class="c1"># Prevents git from committing large files</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-added-large-files</span>
</code></pre></div></div>

<p>Install the pre-commit hooks with <code class="language-plaintext highlighter-rouge">pre-commit install</code>. This needs to be done once per git repository. Afterwards, you can run <code class="language-plaintext highlighter-rouge">pre-commit run --all-files</code> to let the pre-commits do their job. Note: you only run this command if you have added new pre-commits. In other cases, all pre-commits are automatically executed when you use <code class="language-plaintext highlighter-rouge">git commit ...</code>. In this case, the automatic execution limits to the changed files.</p>

<p>Depending on the project you see no, some, or a lot of changed files. For example, when I ran it on my <a href="https://github.com/Roald87/TwincatTutorials">TwinCAT Tutorial repo</a> I saw the following:</p>

<p><a href="/assets/2022-pre-commit/changes-twincat-tutorial.png"><picture><source srcset="/generated/2022-pre-commit/changes-twincat-tutorial-300-f97079444.webp 300w, /generated/2022-pre-commit/changes-twincat-tutorial-500-f97079444.webp 500w, /generated/2022-pre-commit/changes-twincat-tutorial-720-f97079444.webp 720w" type="image/webp"><source srcset="/generated/2022-pre-commit/changes-twincat-tutorial-300-74b8aaf4c.png 300w, /generated/2022-pre-commit/changes-twincat-tutorial-500-74b8aaf4c.png 500w, /generated/2022-pre-commit/changes-twincat-tutorial-720-74b8aaf4c.png 720w" type="image/png"><img src="/generated/2022-pre-commit/changes-twincat-tutorial-800-74b8aaf4c.png" alt="pre-commit changes to the twincat tutorial repo" width="824" height="283"></picture></a></p>

<p>For each git hook, you see if it had files to check. If that was the case, you see if any files were changed.</p>

<p>Below I show an example where two hooks were triggered. Here both the leading tabs remover failed (as shown in the screenshot), but also the trailing white space one failed (not shown). Below are the differences I saw afterward in SourceTree:</p>

<p><a href="/assets/2022-pre-commit/after-running-sample-pre-commit.png"><picture><source srcset="/generated/2022-pre-commit/after-running-sample-pre-commit-300-42c2bb735.webp 300w, /generated/2022-pre-commit/after-running-sample-pre-commit-355-42c2bb735.webp 355w" type="image/webp"><source srcset="/generated/2022-pre-commit/after-running-sample-pre-commit-300-8cf5b80f8.png 300w, /generated/2022-pre-commit/after-running-sample-pre-commit-355-8cf5b80f8.png 355w" type="image/png"><img src="/generated/2022-pre-commit/after-running-sample-pre-commit-355-8cf5b80f8.png" alt="git diff after running the sample pre-commit. Here trailing white spaces have been removed and tabs are replaced by spaces." width="355" height="136"></picture></a></p>

<p>You see that it removed a trailing space after the <code class="language-plaintext highlighter-rouge">CASE _state OF</code>. Additionally, the file had a mix of tabs and spaces. The tabs were replaced by spaces. These changes were made by the <code class="language-plaintext highlighter-rouge">trailing-whitespace</code> and <code class="language-plaintext highlighter-rouge">twincat-leading-tabs-remover</code> respectively. For a full list of all the changes, you can see the differences of <a href="https://github.com/Roald87/TwincatTutorials/pull/2/commits/91041e5f94d44095ce070381645609bf82ce52d8">this commit</a>.</p>

<h3 id="hooks-what-and-why">Hooks what and why</h3>

<p>Here I go a little deeper into what the hooks do and why you would want to use them.</p>

<p><code class="language-plaintext highlighter-rouge">twincat-leading-tabs-remover</code></p>

<p>What: replaces all leading tabs with four spaces.</p>

<p>Why: consistency. Also in some editors, the length of a tab can differ from the length of four spaces.</p>

<p><code class="language-plaintext highlighter-rouge">twincat-lineids-remover</code></p>

<p>What: removes LineIDs from a POU file.</p>

<p>Why: they are only useful locally. When uploaded to source control they only cause visual clutter. For more information <a href="https://cookncode.com/twincat/2021/06/07/tc-source-control-tips#2-creating-independent-files">see point 4</a></p>

<p><code class="language-plaintext highlighter-rouge">twincat-xml-format</code></p>

<p>What: formats the <code class="language-plaintext highlighter-rouge">.tmc</code> and <code class="language-plaintext highlighter-rouge">.tcp</code> files with newlines and indentation.</p>

<p>Why: makes these files readable for humans. Normally TwinCAT doesn’t put any newlines or indentations in these files. Useful if you would like to have these files in source control and see clear differences.</p>

<p><code class="language-plaintext highlighter-rouge">check-fixed-library-versions</code></p>

<p>What: checks if there are TwinCAT libraries whose versions are not fixed or explicitly set to the latest version.</p>

<p>Why: ensures that your software behaves the same, even if you install newer library version on your system.</p>

<p><code class="language-plaintext highlighter-rouge">trailing-whitespace</code></p>

<p>What: removes spaces and tabs at the end of lines.</p>

<p>Why: whitespace at the end of a line does not influence code execution; you can add or remove as many as you’d like. But, they show up as (useless) changes if someone adds some or removes them.</p>

<p><code class="language-plaintext highlighter-rouge">check-yaml</code></p>

<p>What: checks that programs can read your YAML file. For example, the <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> one.</p>

<p>Why: ensures that your YAML files do not break.</p>

<p><code class="language-plaintext highlighter-rouge">check-added-large-files</code>:</p>

<p>What: prevents git from adding large files to its history.</p>

<p>Why: to prevent your git tree from becoming huge. Saves time for new users when they download the repo for the first time. Large, non-text files, such as images or binary files usually contain no useful diffs. You want to commit these files using <a href="https://git-lfs.github.com/">Git LFS</a>.</p>

<h2 id="developing-your-own-hooks">Developing your own hooks</h2>

<p>If you would like to develop your own hooks there are two options: local and remote repo-based hooks. Local hooks are quite easy to set up, but they can only be used in the repo where they are saved. On the other hand, remote-based hooks can be shared across many projects. <a href="https://github.com/pcdshub/pre-commit-hooks">The TwinCAT hooks</a> you saw earlier, is an example of a remote hook.</p>

<p>You can <a href="https://pre-commit.com/#supported-languages">use many languages</a> to develop your hooks. Most languages need a working installation of that specific language on the system where the hooks are executed. The exceptions are node, python, and ruby. For these languages, no existing installation is needed. Hooks developed in these three languages set up their own (node, python, ruby) environment when first executed. On subsequent runs, the environment is reused.</p>

<p>Remote hooks also need to be a valid git repo. That is because pre-commit tries to do a <code class="language-plaintext highlighter-rouge">git clone ...</code> of the repo URL you supplied in the <code class="language-plaintext highlighter-rouge">.pre-commit.yaml</code> file. To make a remote hook, see the <a href="https://github.com/pcdshub/pre-commit-hooks">TwinCAT hooks repo</a> as an example.</p>

<h3 id="local-hook-example">Local hook example</h3>

<p>Developing a local hook is quite straightforward. I’ll explain how to do it by making a hook that checks if all links on this blog start with <code class="language-plaintext highlighter-rouge">https</code>. If they don’t, it replaces <code class="language-plaintext highlighter-rouge">http</code> with <code class="language-plaintext highlighter-rouge">https</code>.</p>

<p>First I added a file called <a href="https://github.com/Roald87/roald87.github.io/blob/main/.pre-commit-config.yaml"><code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code></a> with the following content. See the comments for their meaning.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">repos</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">local</span>
    <span class="na">hooks</span><span class="pi">:</span>
      <span class="c1"># Name of the hook</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-https</span>
        <span class="c1"># Hook name shown during hook execution</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">check if all links are https</span>
        <span class="c1"># Where pre-commit can find the script it should call and how it should call it</span>
        <span class="na">entry</span><span class="pi">:</span> <span class="s">python _hooks/check_https.py</span>
        <span class="c1"># The language to use, in this case we're using a language present on the system.</span>
        <span class="c1"># Using python as an argument would work as well</span>
        <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
        <span class="c1"># A valid regular expression pattern to define which files should be passed to `check_https.py`</span>
        <span class="na">files</span><span class="pi">:</span> <span class="s1">'</span><span class="s">.*\.(md|markdown)'</span>
</code></pre></div></div>

<p>When pre-commit runs this local hook, it first searches for files that conform to the regular expression pattern mentioned in <code class="language-plaintext highlighter-rouge">files:</code>. Then it calls the command mentioned under <code class="language-plaintext highlighter-rouge">entry</code> with each filename. For example, it finds the files <code class="language-plaintext highlighter-rouge">README.md</code> and <code class="language-plaintext highlighter-rouge">about.md</code>. Then it calls <code class="language-plaintext highlighter-rouge">check_https.py</code> with <code class="language-plaintext highlighter-rouge">python _hooks/check_https.py README.md about.md</code>.</p>

<p>Next, I created a new file called <a href="https://github.com/Roald87/roald87.github.io/blob/main/_hooks/check_https.py"><code class="language-plaintext highlighter-rouge">_hooks/check_https.py</code></a> with the following content. See comments for the meaning.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python
</span>
<span class="kn">import</span> <span class="nn">argparse</span>

<span class="k">def</span> <span class="nf">fix_file</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
    <span class="c1"># Open the file
</span>    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">fd</span><span class="p">:</span>
        <span class="n">original_lines</span> <span class="o">=</span> <span class="n">fd</span><span class="p">.</span><span class="n">readlines</span><span class="p">()</span>
    <span class="n">new_lines</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">changed</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="c1"># For each line in the file check if a https:// is found.
</span>    <span class="c1"># If so, replace it with https://
</span>    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">original_lines</span><span class="p">:</span>
        <span class="k">if</span> <span class="s">"https://"</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
            <span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">"https://"</span><span class="p">,</span> <span class="s">"https://"</span><span class="p">)</span>
            <span class="n">changed</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="n">new_lines</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>

    <span class="c1"># If a line was changed, print a message in the console and overwrite the
</span>    <span class="c1"># original file with the fixed one.
</span>    <span class="k">if</span> <span class="n">changed</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Fixing </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">fd</span><span class="p">:</span>
            <span class="n">fd</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">new_lines</span><span class="p">))</span>


<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="c1"># Parse the filename arguments:
</span>    <span class="c1"># e.g. Namespace(filenames=['README.md', 'about.md'])
</span>    <span class="k">if</span> <span class="n">args</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="p">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
        <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'filenames'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s">'*'</span><span class="p">)</span>
        <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="n">parse_args</span><span class="p">()</span>
    <span class="c1"># For each filename execute the file fixer on it
</span>    <span class="k">try</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">args</span><span class="p">.</span><span class="n">filenames</span><span class="p">:</span>
            <span class="n">fix_file</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
        <span class="c1"># Return 0, which means the hook executed successfully and pre-commit is happy
</span>        <span class="k">return</span> <span class="mi">0</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">exc</span><span class="p">)</span>
        <span class="c1"># If something went wrong return an error code other than 0 and pre-commit
</span>        <span class="c1"># then knows something went wrong
</span>        <span class="k">return</span> <span class="mi">1</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="nb">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<p>If you now run <code class="language-plaintext highlighter-rouge">pre-commit run --all-files</code> you should see <a href="https://github.com/Roald87/roald87.github.io/commit/85cf128b4a82f1807d8fbbcb74fa57825408c9aa">all the files</a> it changed.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; pre-commit run --all-files
check if all links are https.................................................Failed
- hook id: check-https
- files were modified by this hook

Fixing _posts/2021-09-13-units.markdown
Fixing _posts/2021-06-07-tc-source-control-tips.markdown
Fixing _posts/2021-08-17-tc-simulation.markdown
Fixing tclinks.md
</code></pre></div></div>

<h2 id="further-ideas">Further ideas</h2>

<p>Other ideas could be to use <a href="https://prettier.io/">Prettier</a> to format JavaScript, HTML, or CSS files from HMI projects. Unfortunately, TwinCAT saves the HMI pages as <code class="language-plaintext highlighter-rouge">.content</code> and <code class="language-plaintext highlighter-rouge">.view</code> files. These files are not recognized as HTML files by prettier, but you could probably make it work by temporarily renaming these files to <code class="language-plaintext highlighter-rouge">.html</code> and then running prettier.</p>

<p>Pre-commits are automatically executed locally whenever you commit something. But you can also add pre-commit to your CI workflow. For example, use <a href="https://pre-commit.ci/">prettier.ci</a> to <a href="https://github.com/Roald87/TwinCatChangelog/pull/23">automatically format markdown files of a pull request</a>.</p>

<p><a href="/assets/2022-pre-commit/precommit_ci.png"><picture><source srcset="/generated/2022-pre-commit/precommit_ci-300-e5a6595d1.webp 300w, /generated/2022-pre-commit/precommit_ci-500-e5a6595d1.webp 500w, /generated/2022-pre-commit/precommit_ci-720-e5a6595d1.webp 720w" type="image/webp"><source srcset="/generated/2022-pre-commit/precommit_ci-300-2f9b5aeba.png 300w, /generated/2022-pre-commit/precommit_ci-500-2f9b5aeba.png 500w, /generated/2022-pre-commit/precommit_ci-720-2f9b5aeba.png 720w" type="image/png"><img src="/generated/2022-pre-commit/precommit_ci-800-2f9b5aeba.png" alt="pre-commit integration in github actions" width="1023" height="126"></picture></a>
<a href="/assets/2022-pre-commit/precommit_ci_changes.png"><picture><source srcset="/generated/2022-pre-commit/precommit_ci_changes-300-a10e9d105.webp 300w, /generated/2022-pre-commit/precommit_ci_changes-500-a10e9d105.webp 500w, /generated/2022-pre-commit/precommit_ci_changes-720-a10e9d105.webp 720w" type="image/webp"><source srcset="/generated/2022-pre-commit/precommit_ci_changes-300-a8cafaa06.png 300w, /generated/2022-pre-commit/precommit_ci_changes-500-a8cafaa06.png 500w, /generated/2022-pre-commit/precommit_ci_changes-720-a8cafaa06.png 720w" type="image/png"><img src="/generated/2022-pre-commit/precommit_ci_changes-800-a8cafaa06.png" alt="changes made by pre-commit.ci" width="1007" height="685"></picture></a></p>

<p>Have you already used pre-commits for your (TwinCAT) projects? Or do you have other ideas? Let me know in the comments below.</p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Earlier I talked about how you can do version control of your TwinCAT code with git. In this post, I want to show a neat feature of git which I didn’t mention last time: pre-commits. Pre-commits can format, lint, or do static code analyses on your code before committing. One pre-commit is available for structured text files. But, pre-commits are also available for markdown, HTML, or JavaScript files.]]></summary></entry><entry><title type="html">How to work with the RowClassesProvider?</title><link href="https://cookncode.com/twincat/2022/01/31/row-classes-provider.html" rel="alternate" type="text/html" title="How to work with the RowClassesProvider?" /><published>2022-01-31T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/01/31/row-classes-provider</id><content type="html" xml:base="https://cookncode.com/twincat/2022/01/31/row-classes-provider.html"><![CDATA[<p>Recently a user on the <a href="https://www.linkedin.com/groups/1860933/">LinkedIn TwinCAT forum</a> shared and example how to use the RowClassesProvider. When I tried the example and went looking for the documentation, there was none to be found! On the whole internet, there is <a href="https://gist.github.com/benhar-dev/2216bf3001aa49097037f759087667e8">one (1!) example</a> which sort of shows the function. In this post I will show what the RowClassesProvider does and how you can use it to dynamically color data grids.</p>

<ul>
  <li>Example code for TcHMI v1.10: <a href="https://github.com/Roald87/TwincatTutorials/tree/main/Datagrid/Datagrid">GitHub repo</a>, <a href="https://downgit.github.io/#/home?url=https://github.com/Roald87/TwincatTutorials/tree/main/Datagrid/Datagrid">direct download</a>.</li>
</ul>

<h2 id="result">Result</h2>

<p>Before diving in, this will be the result:</p>

<p><img src="/assets/2022-01-31-row-classes-provider/all_effects_datagrid.gif" alt="all different styles showcased" /></p>

<h2 id="setup">Setup</h2>

<p>In order to explore what RowClassesProvider does let’s make a small sample project.</p>

<ol>
  <li>Create a new HMI project in Visual Studio by going to <strong>File &gt; New &gt; Project</strong> and select <strong>TwinCAT HMI &gt; TwinCAT HMI Project</strong>. Give it an appropriate name and select <strong>OK</strong>.</li>
  <li>Open the Desktop.view by double clicking on it.</li>
  <li>From the tool bar, drag a Datagrid into the Desktop.view.
<img src="/assets/2022-01-31-row-classes-provider/drag_datagrid.gif" alt="data grid being dragged into the desktop.view" /></li>
  <li>Add a new JavaScript function by right clicking on your HMI project and select <strong>Add &gt; New Item</strong>.
<a href="/assets/2022-01-31-row-classes-provider/add_new_item.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/add_new_item-300-fbf3a529d.webp 300w, /generated/2022-01-31-row-classes-provider/add_new_item-500-fbf3a529d.webp 500w, /generated/2022-01-31-row-classes-provider/add_new_item-611-fbf3a529d.webp 611w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/add_new_item-300-379115591.png 300w, /generated/2022-01-31-row-classes-provider/add_new_item-500-379115591.png 500w, /generated/2022-01-31-row-classes-provider/add_new_item-611-379115591.png 611w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/add_new_item-611-379115591.png" alt="add new item to HMI project" width="611" height="272"></picture></a></li>
  <li>Then select Function (JavaScript) and give it an appropriate name and select <strong>OK</strong>.
<a href="/assets/2022-01-31-row-classes-provider/add_javascript_function.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/add_javascript_function-300-14c2cf4ae.webp 300w, /generated/2022-01-31-row-classes-provider/add_javascript_function-500-14c2cf4ae.webp 500w, /generated/2022-01-31-row-classes-provider/add_javascript_function-720-14c2cf4ae.webp 720w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/add_javascript_function-300-6b471373a.png 300w, /generated/2022-01-31-row-classes-provider/add_javascript_function-500-6b471373a.png 500w, /generated/2022-01-31-row-classes-provider/add_javascript_function-720-6b471373a.png 720w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/add_javascript_function-786-6b471373a.png" alt="select JavaScript function" width="786" height="480"></picture></a></li>
</ol>

<h2 id="exploring-rowclassesproviders-functionality">Exploring RowClassesProvider’s functionality</h2>

<p>Now we have a sample project it is time to explore what the RowClassesProvider does.</p>

<ol>
  <li>Open the <code class="language-plaintext highlighter-rouge">RowClassesProvider.function.json</code> file of the JavaScript function we just created, with a double click.</li>
  <li>
    <p>In the new window change/add the following things:</p>

    <ul>
      <li>Change the return type to <code class="language-plaintext highlighter-rouge">Array</code>. Later we fill this array to return the styles which should be applied to the Datagrid.</li>
      <li>Change the DisplayName of the first variable to <code class="language-plaintext highlighter-rouge">rowData</code> and select for the Datatype <code class="language-plaintext highlighter-rouge">Any</code>.</li>
      <li>Add a new row and change the DisplayName to <code class="language-plaintext highlighter-rouge">rowIndex</code> and for the Datatype select <code class="language-plaintext highlighter-rouge">Integer</code>.</li>
      <li>Add another new row and change the DisplayName to <code class="language-plaintext highlighter-rouge">rowNumber</code> and for the Datatype select <code class="language-plaintext highlighter-rouge">Integer</code>.</li>
    </ul>

    <p>In the end it should look as shown here:</p>

    <p><a href="/assets/2022-01-31-row-classes-provider/function_io.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/function_io-300-dfa36844d.webp 300w, /generated/2022-01-31-row-classes-provider/function_io-500-dfa36844d.webp 500w, /generated/2022-01-31-row-classes-provider/function_io-720-dfa36844d.webp 720w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/function_io-300-41a2b6894.png 300w, /generated/2022-01-31-row-classes-provider/function_io-500-41a2b6894.png 500w, /generated/2022-01-31-row-classes-provider/function_io-720-41a2b6894.png 720w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/function_io-800-41a2b6894.png" alt="menu after applying all changes" width="917" height="393"></picture></a></p>
  </li>
  <li>Confirm your changes by selecting <strong>OK</strong> and confirm your changes in the additional pop-up.</li>
  <li>
    <p>In case you had the <code class="language-plaintext highlighter-rouge">RowClassesProvider.js</code> file open, you will get a pop-up. The pop-up is caused by the changes you just made. Select <strong>Yes</strong> to reload the file.</p>

    <p><a href="/assets/2022-01-31-row-classes-provider/confirm_reload.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/confirm_reload-300-762b5b16c.webp 300w, /generated/2022-01-31-row-classes-provider/confirm_reload-500-762b5b16c.webp 500w, /generated/2022-01-31-row-classes-provider/confirm_reload-615-762b5b16c.webp 615w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/confirm_reload-300-16386cd27.png 300w, /generated/2022-01-31-row-classes-provider/confirm_reload-500-16386cd27.png 500w, /generated/2022-01-31-row-classes-provider/confirm_reload-615-16386cd27.png 615w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/confirm_reload-615-16386cd27.png" width="615" height="229"></picture></a></p>
  </li>
</ol>

<p>If all went <strong>OK</strong> your code in <code class="language-plaintext highlighter-rouge">RowClassesProvider.js</code> should look as follows (ignoring the <code class="language-plaintext highlighter-rouge">&lt;reference path ...</code> lines):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">TcHmi</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">RowClassesProvider</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">rowNumber</span><span class="p">)</span> <span class="p">{};</span>

  <span class="nx">TcHmi</span><span class="p">.</span><span class="nx">Functions</span><span class="p">.</span><span class="nx">registerFunction</span><span class="p">(</span><span class="dl">"</span><span class="s2">RowClassesProvider</span><span class="dl">"</span><span class="p">,</span> <span class="nx">RowClassesProvider</span><span class="p">);</span>
<span class="p">})(</span><span class="nx">TcHmi</span><span class="p">);</span>
</code></pre></div></div>

<p>This function takes three inputs: <code class="language-plaintext highlighter-rouge">rowData</code>, <code class="language-plaintext highlighter-rouge">rowIndex</code> and <code class="language-plaintext highlighter-rouge">rowNumber</code>. The inputs contain information about the selected row of a data grid. To see what this function does, add some logging code to the function:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">TcHmi</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">RowClassesProvider</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">rowNumber</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Data: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">rowData</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Index: </span><span class="p">${</span><span class="nx">rowIndex</span><span class="p">}</span><span class="s2">\nNumber: </span><span class="p">${</span><span class="nx">rowNumber</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="nx">TcHmi</span><span class="p">.</span><span class="nx">Functions</span><span class="p">.</span><span class="nx">registerFunction</span><span class="p">(</span><span class="dl">"</span><span class="s2">RowClassesProvider</span><span class="dl">"</span><span class="p">,</span> <span class="nx">RowClassesProvider</span><span class="p">);</span>
<span class="p">})(</span><span class="nx">TcHmi</span><span class="p">);</span>
</code></pre></div></div>

<p>Now every time this function is called, it will print in the console with which parameters the function was called.</p>

<p>Save the function. After changing the function the designer shows a prompt that it needs to be reloaded. Click on the yellow ribbon to reload the designer.</p>

<p><a href="/assets/2022-01-31-row-classes-provider/designer_reload_prompt.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/designer_reload_prompt-300-459e81575.webp 300w, /generated/2022-01-31-row-classes-provider/designer_reload_prompt-491-459e81575.webp 491w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/designer_reload_prompt-300-19e9dd469.png 300w, /generated/2022-01-31-row-classes-provider/designer_reload_prompt-491-19e9dd469.png 491w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/designer_reload_prompt-491-19e9dd469.png" alt="designer reload prompt" width="491" height="60"></picture></a></p>

<p>Next we link this function to our Datagrid. Open the Desktop.view and</p>

<ol>
  <li>Select the TcHmiDataGrid.</li>
  <li>In the Properties window under <strong>Data &gt; RowClassesProvider</strong> press the <code class="language-plaintext highlighter-rouge">...</code> button.</li>
  <li>
    <p>Then find the function we just created and drag it into the right window titled <strong>Functions</strong>.</p>

    <p><a href="/assets/2022-01-31-row-classes-provider/link_function.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/link_function-300-7db3ac63d.webp 300w, /generated/2022-01-31-row-classes-provider/link_function-500-7db3ac63d.webp 500w, /generated/2022-01-31-row-classes-provider/link_function-720-7db3ac63d.webp 720w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/link_function-300-c59d64ae9.png 300w, /generated/2022-01-31-row-classes-provider/link_function-500-c59d64ae9.png 500w, /generated/2022-01-31-row-classes-provider/link_function-720-c59d64ae9.png 720w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/link_function-800-c59d64ae9.png" alt="link row classes provider to data grid" width="902" height="420"></picture></a></p>
  </li>
  <li>Select <strong>OK</strong> to confirm your changes.</li>
</ol>

<h2 id="seeing-it-in-action">Seeing it in action</h2>

<ol>
  <li>Open the Live-View window from <strong>TwinCAT HMI &gt; Windows &gt; TwinCAT HMI Live-View</strong>.</li>
  <li>Open the developer tools, by clicking on the gear icon on the top.
<a href="/assets/2022-01-31-row-classes-provider/live-view_datagrid_log.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/live-view_datagrid_log-300-866ba0125.webp 300w, /generated/2022-01-31-row-classes-provider/live-view_datagrid_log-500-866ba0125.webp 500w, /generated/2022-01-31-row-classes-provider/live-view_datagrid_log-523-866ba0125.webp 523w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/live-view_datagrid_log-300-b0c2528a4.png 300w, /generated/2022-01-31-row-classes-provider/live-view_datagrid_log-500-b0c2528a4.png 500w, /generated/2022-01-31-row-classes-provider/live-view_datagrid_log-523-b0c2528a4.png 523w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/live-view_datagrid_log-523-b0c2528a4.png" alt="log" width="523" height="656"></picture></a></li>
</ol>

<p>In the developer tools you see the results of the <code class="language-plaintext highlighter-rouge">console.log</code> calls in our function. The <code class="language-plaintext highlighter-rouge">rowData</code> variable contains all the column data of a single row. The <code class="language-plaintext highlighter-rouge">rowIndex</code> and <code class="language-plaintext highlighter-rouge">rowNumber</code> both contain the number of the row. I’m not sure why there are two variables containing the same information.</p>

<p>On the initial load of the Desktop.view, all rows were called, However, if later we select a row and make a change, only that row will show up in the log. Here I changed the editable data on the second row to <strong>cookncode</strong>.</p>

<p><a href="/assets/2022-01-31-row-classes-provider/change_editable_data.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/change_editable_data-300-d11b264fe.webp 300w, /generated/2022-01-31-row-classes-provider/change_editable_data-489-d11b264fe.webp 489w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/change_editable_data-300-b96561924.png 300w, /generated/2022-01-31-row-classes-provider/change_editable_data-489-b96561924.png 489w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/change_editable_data-489-b96561924.png" alt="log" width="489" height="580"></picture></a></p>

<p>From this example you might start to see how we can use the RowClassesProvider. Basically we can take actions based on any of the received input parameters. Be it any of the <code class="language-plaintext highlighter-rouge">rowData</code> columns, or the index of the row.</p>

<h2 id="applying-css-styles-to-the-datagrid">Applying CSS styles to the Datagrid</h2>

<p>You may recall that when we defined the <code class="language-plaintext highlighter-rouge">RowClassesProvider</code> function, we also specified it returned an <code class="language-plaintext highlighter-rouge">Array</code>. In this section I will fill this array with CSS styles, such that that the datagrid can dynamically change its formatting.</p>

<p>First we will define a custom style in a CSS file. To add a CSS file, right click on the HMI project and select <strong>Add &gt; New Item</strong> and select <strong>Cascading Style Sheet (CSS)</strong> and click on <strong>Add</strong>.</p>

<p>For example we can change the color of a table cell when no data is present. In order to do this we first define a new style by adding the following lines to the <code class="language-plaintext highlighter-rouge">.css</code> file:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* Change the first column's background to orange */</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span><span class="nc">.missing-input</span> <span class="nt">td</span><span class="o">[</span><span class="nt">data-column-name</span><span class="o">=</span><span class="s1">"Test1"</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#f76d16</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">#TcHmiDatagrid</code> means the style is for a TwinCAT HMI datagrid.</li>
  <li><a href="https://www.w3schools.com/tags/tag_tr.asp"><code class="language-plaintext highlighter-rouge">tr</code></a> denotes a row in a table.</li>
  <li><code class="language-plaintext highlighter-rouge">.missing-input</code> is the name of the style.</li>
  <li><a href="https://www.w3schools.com/tags/tag_td.asp"><code class="language-plaintext highlighter-rouge">td</code></a> denotes a table data cell.</li>
  <li><code class="language-plaintext highlighter-rouge">data-column-name="Test1"</code> means we apply the style only to a specific column with the name <code class="language-plaintext highlighter-rouge">"Test1"</code>. I will show in a minute where you can find the column name.</li>
  <li><code class="language-plaintext highlighter-rouge">background-color: #F76D16;</code> is the newly applied background color.</li>
</ul>

<p>In order to apply the style under certain conditions we change the <code class="language-plaintext highlighter-rouge">RowClassesProvider</code> function as follows:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">TcHmi</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">RowClassesProvider</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">rowNumber</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">cssStyles</span> <span class="o">=</span> <span class="p">[];</span> <span class="c1">// Collected CSS styles which will be returned at the end</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">Test1</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// If the first column (Test1) is empty</span>
      <span class="nx">cssStyles</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">missing-input</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// add the missing-input style to the array</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nx">cssStyles</span><span class="p">;</span>
  <span class="p">};</span>

  <span class="nx">TcHmi</span><span class="p">.</span><span class="nx">Functions</span><span class="p">.</span><span class="nx">registerFunction</span><span class="p">(</span><span class="dl">"</span><span class="s2">RowClassesProvider</span><span class="dl">"</span><span class="p">,</span> <span class="nx">RowClassesProvider</span><span class="p">);</span>
<span class="p">})(</span><span class="nx">TcHmi</span><span class="p">);</span>
</code></pre></div></div>

<p>Now the <code class="language-plaintext highlighter-rouge">RowClassesProvider</code> returns an array with the style names which should be applied. For now the <code class="language-plaintext highlighter-rouge">missing-input</code> style is added in case the input of the first column is empty.</p>

<p>Before we see it in action, we first need to add another row to our Datagrid, since currently all columns contain data. Open the Desktop.view and select the <code class="language-plaintext highlighter-rouge">...</code> of the <strong>SrcData</strong> in the Properties window.</p>

<p><a href="/assets/2022-01-31-row-classes-provider/source_data.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/source_data-300-48b48e131.webp 300w, /generated/2022-01-31-row-classes-provider/source_data-500-48b48e131.webp 500w, /generated/2022-01-31-row-classes-provider/source_data-704-48b48e131.webp 704w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/source_data-300-678fa283a.png 300w, /generated/2022-01-31-row-classes-provider/source_data-500-678fa283a.png 500w, /generated/2022-01-31-row-classes-provider/source_data-704-678fa283a.png 704w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/source_data-704-678fa283a.png" alt="edit source data for data grid" width="704" height="493"></picture></a></p>

<p>Add four new columns, where Test1 is just an empty string. Note that here you can see the names of each of the columns. You can also give them more descriptive names for your application. It is also possible to link the source data to a struct in your PLC project, but this is outside of the scope of this tutorial. After you’ve made the changes click <strong>OK</strong> and open the live view.</p>

<p>If all went well, the first column of the third row should be orange.</p>

<p><a href="/assets/2022-01-31-row-classes-provider/column1_missing_data.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/column1_missing_data-300-a31efc332.webp 300w, /generated/2022-01-31-row-classes-provider/column1_missing_data-387-a31efc332.webp 387w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/column1_missing_data-300-2c2f7309c.png 300w, /generated/2022-01-31-row-classes-provider/column1_missing_data-387-2c2f7309c.png 387w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/column1_missing_data-387-2c2f7309c.png" width="387" height="135"></picture></a></p>

<p>Styles can also be applied dynamically after for example toggling a checkbox, based on the value of a text field or the style of a whole row can be changed based on the state of a single column. To do so, change the <code class="language-plaintext highlighter-rouge">.css</code> file as follows:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/*Change the background color of the first column to orange*/</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span><span class="nc">.missing-input</span> <span class="nt">td</span><span class="o">[</span><span class="nt">data-column-name</span><span class="o">=</span><span class="s1">"Test1"</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#f76d16</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">/*Change the second column's background to orange*/</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span><span class="nc">.below-zero</span> <span class="nt">td</span><span class="o">[</span><span class="nt">data-column-name</span><span class="o">=</span><span class="s1">"Test2"</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#f76d16</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">/*A default background color for the second column*/</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span> <span class="nt">td</span><span class="o">[</span><span class="nt">data-column-name</span><span class="o">=</span><span class="s1">"Test2"</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#a0d5e8</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">/*Change the background color of an entire row if a check box' state is changed*/</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span><span class="nc">.not-checked</span> <span class="nt">td</span><span class="nd">::after</span> <span class="p">{</span>
  <span class="nl">content</span><span class="p">:</span> <span class="s1">""</span><span class="p">;</span>
  <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
  <span class="nl">top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">right</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">bottom</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">left</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">10000px</span><span class="p">;</span>
  <span class="nl">pointer-events</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
  <span class="nl">z-index</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="n">rgba</span><span class="p">(</span><span class="m">215</span><span class="p">,</span> <span class="m">249</span><span class="p">,</span> <span class="m">251</span><span class="p">,</span> <span class="m">0.5</span><span class="p">);</span>
<span class="p">}</span>

<span class="c">/*Make the text in the fourth column bold and change the color*/</span>
<span class="nf">#TcHmiDatagrid</span> <span class="nt">tr</span><span class="nc">.bold</span> <span class="nt">td</span><span class="o">[</span><span class="nt">data-column-name</span><span class="o">=</span><span class="s1">"Test4"</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span>
  <span class="nl">color</span><span class="p">:</span> <span class="m">#f76d16</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>and change <code class="language-plaintext highlighter-rouge">RowClassesProvider</code> into</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">TcHmi</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">RowClassesProvider</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">rowNumber</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">cssStyles</span> <span class="o">=</span> <span class="p">[];</span> <span class="c1">// Collected CSS styles which will be returned at the end</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">Test1</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// If the first column (Test1) is empty</span>
      <span class="nx">cssStyles</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">missing-input</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// add the missing-input style to the array</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">Test2</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// If the value of the second column is negative</span>
      <span class="nx">cssStyles</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">below-zero</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// add the below-zero style</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">Test3</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// If the checkbox in the third column is not checked</span>
      <span class="nx">cssStyles</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-checked</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">Test4</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">cookncode</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// if the text in the fourth column is cookncode</span>
      <span class="nx">cssStyles</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">bold</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nx">cssStyles</span><span class="p">;</span>
  <span class="p">};</span>

  <span class="nx">TcHmi</span><span class="p">.</span><span class="nx">Functions</span><span class="p">.</span><span class="nx">registerFunction</span><span class="p">(</span><span class="dl">"</span><span class="s2">RowClassesProvider</span><span class="dl">"</span><span class="p">,</span> <span class="nx">RowClassesProvider</span><span class="p">);</span>
<span class="p">})(</span><span class="nx">TcHmi</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="result-1">Result</h2>

<p><img src="/assets/2022-01-31-row-classes-provider/all_effects_datagrid.gif" alt="all different styles showcased" /></p>

<h2 id="final-tip-inspiration-and-default-styles">Final tip: inspiration and default styles</h2>

<p>If you’re looking for inspiration what you can change or what the default styles of Beckhoff’s controls are, you can find these if you open the developer console. Then go to the Sources tab and navigate to a folder. Here I show the Base theme style for the Datagrid.</p>

<p><a href="/assets/2022-01-31-row-classes-provider/inspiration.png"><picture><source srcset="/generated/2022-01-31-row-classes-provider/inspiration-300-8c474de41.webp 300w, /generated/2022-01-31-row-classes-provider/inspiration-500-8c474de41.webp 500w, /generated/2022-01-31-row-classes-provider/inspiration-720-8c474de41.webp 720w" type="image/webp"><source srcset="/generated/2022-01-31-row-classes-provider/inspiration-300-02704629d.png 300w, /generated/2022-01-31-row-classes-provider/inspiration-500-02704629d.png 500w, /generated/2022-01-31-row-classes-provider/inspiration-720-02704629d.png 720w" type="image/png"><img src="/generated/2022-01-31-row-classes-provider/inspiration-786-02704629d.png" alt="beckhoff's default styles" width="786" height="688"></picture></a></p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Recently a user on the LinkedIn TwinCAT forum shared and example how to use the RowClassesProvider. When I tried the example and went looking for the documentation, there was none to be found! On the whole internet, there is one (1!) example which sort of shows the function. In this post I will show what the RowClassesProvider does and how you can use it to dynamically color data grids.]]></summary></entry><entry><title type="html">Tips and tricks for TwinCAT</title><link href="https://cookncode.com/twincat/2022/01/09/tc-quicktips.html" rel="alternate" type="text/html" title="Tips and tricks for TwinCAT" /><published>2022-01-09T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2022/01/09/tc-quicktips</id><content type="html" xml:base="https://cookncode.com/twincat/2022/01/09/tc-quicktips.html"><![CDATA[<p>Over the years I’ve come across some features of TwinCAT or programming in general which can improve your code or your coding experience. Read on to level up your TwinCAT game in 2022!</p>

<ul>
  <li>Don’t repeat yourself, often abbreviated as <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>, is a saying in software development to denote unnecessary repeating code. One tell-tale sign if you write non-DRY code, is that you copy and paste a lot of code.</li>
  <li>
    <p>You can activate multi-line editing by pressing the <kbd>Alt</kbd> key and dragging your mouse across several lines. If you use this function a lot, then it is a sign that your code is probably not <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>. So it is time to think about how to refactor your code.</p>

    <p><img src="/assets/2022-01-09-tc-quicktips/multiline_editing.gif" alt="multi-line editing with Alt key" /></p>
  </li>
  <li>A <code class="language-plaintext highlighter-rouge">_</code> can be used as a thousand-separator. For example: <code class="language-plaintext highlighter-rouge">number : INT := 1_000_000;</code>. Or you can use scientific notation if most decimals are zeros: <code class="language-plaintext highlighter-rouge">number : INT := 1E6;</code>.</li>
  <li>
    <p>(TwinCAT 4024+) Use <kbd>Ctrl</kbd> + <kbd>K</kbd>, <kbd>Ctrl</kbd> + <kbd>C</kbd> to comment one or more lines and <kbd>Ctrl</kbd> + <kbd>K</kbd>, <kbd>Ctrl</kbd> + <kbd>U</kbd> to uncomment them again. Check InfoSys for <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/8116040203.html?id=3573376012774111267">more hotkeys</a>.</p>

    <p><img src="/assets/2022-01-09-tc-quicktips/commenting.gif" alt="comment one or more lines" /></p>
  </li>
  <li>In a <code class="language-plaintext highlighter-rouge">CASE</code> statement you can use multiple cases <code class="language-plaintext highlighter-rouge">1, 3, 61</code> or a whole range <code class="language-plaintext highlighter-rouge">1..10</code> for a single case. For example:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	// Instead of this
	CASE aCase OF
	    1: Method1();
	    2: AnotherMethod();
	    3: AnotherMethod();
	    4: AnotherMethod();
	    6: Method1();
	END_CASE

	// You can do this
	CASE aCase OF
	    1, 6: Method1();
	    2..4: AnotherMethod();
	END_CASE
</code></pre></div></div>

<ul>
  <li>Using numbers to denote a case is usually not the best way to denote a case. You can make cases self-documenting by creating an <a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tcplccontrol/html/tcplcctrl_enum.htm&amp;id=">ENUM</a> for each case.</li>
  <li>There are implicit enums or local enums in TwinCAT. They can be used if you only need an enum in a single function block. You can define them as follows: <code class="language-plaintext highlighter-rouge">colors : (Red, Blue, Green);</code>. See for more info <a href="https://alltwincat.com/2021/11/16/local-enumerations/">this AllTwinCAT post</a>.</li>
  <li>If you have a method or a function which returns a boolean, try to avoid the usage of an <code class="language-plaintext highlighter-rouge">IF</code> statement. Since you’re returning a boolean, you can often do this in a single line:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	// Do not do this
	METHOD LargerThenTwelve : BOOL
	VAR_INPUT
		number : INT;
	END_VAR

	IF number &gt; 12 THEN
		LargerThenTwelve := TRUE;
	ELSE
		LargerThenTwelve := FALSE;
	END_IF

	// Do this
	METHOD LargerThenTwelve : BOOL
	VAR_INPUT
		number : INT;
	END_VAR

	LargerThenTwelve := number &gt; 12;
</code></pre></div></div>

<ul>
  <li>Use <a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529600907.html&amp;id="><code class="language-plaintext highlighter-rouge">{attribute 'call_after_init'}</code></a> to call a method once <em>after</em> a function block was initialized. You can use this, instead of a <code class="language-plaintext highlighter-rouge">bInitialized</code> flag in your code, which inevitability you forget to set to <code class="language-plaintext highlighter-rouge">TRUE</code> at least once 😉.</li>
  <li>Prevent the usage of <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529453451.html?id=5839194631499501145"><code class="language-plaintext highlighter-rouge">POINTER</code></a>s. They can lead to some pretty unpredictable behavior since you’re accessing a piece of memory directly. A better alternative is to use <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529458827.html?id=2716630061017907414"><code class="language-plaintext highlighter-rouge">REFERENCE</code></a>, because it has a cleaner syntax and type safety. Although also references need to be checked for validity as <a href="https://cookncode.com/twincat/2021/02/07/preventing-page-faults-from-references.html">I wrote about earlier</a>. For the health and safety of you and your code, I recommend to only use them if you’re memory constrained for example.</li>
  <li>Try to prevent method calls from multiple tasks, since it makes debugging difficult. If you really have to do it, use <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/45844579955484184843.html?id=2972649925198044529">this</a>.</li>
  <li>
    <p>You can use <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529343371.html?id=6082056588980779846">bit access</a> for integer variables. For example, <code class="language-plaintext highlighter-rouge">variable.2 := TRUE</code>, sets the third bit to <code class="language-plaintext highlighter-rouge">TRUE</code>. There is read access as well. You can for example check if some bit of a number is true by <code class="language-plaintext highlighter-rouge">IF variable.3 THEN ...</code>. This method allows you to decipher an error code where each bit represents a specific error. Note that <a href="https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529442699.html?id=5627824830837717856">bit access can be a factor five slower</a> than using a bit mask.</p>

    <p><a href="/assets/2022-01-09-tc-quicktips/timing_bit_access.png"><picture><source srcset="/generated/2022-01-09-tc-quicktips/timing_bit_access-300-3e99c82be.webp 300w, /generated/2022-01-09-tc-quicktips/timing_bit_access-500-3e99c82be.webp 500w, /generated/2022-01-09-tc-quicktips/timing_bit_access-720-3e99c82be.webp 720w" type="image/webp"><source srcset="/generated/2022-01-09-tc-quicktips/timing_bit_access-300-8e92e97c1.png 300w, /generated/2022-01-09-tc-quicktips/timing_bit_access-500-8e92e97c1.png 500w, /generated/2022-01-09-tc-quicktips/timing_bit_access-720-8e92e97c1.png 720w" type="image/png"><img src="/generated/2022-01-09-tc-quicktips/timing_bit_access-800-8e92e97c1.png" width="1182" height="563"></picture></a>
<em>Timing results from <a href="https://github.com/tkucic/codesys_code_execution_speedTests">Toni Kucic</a>.</em></p>
  </li>
</ul>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Over the years I’ve come across some features of TwinCAT or programming in general which can improve your code or your coding experience. Read on to level up your TwinCAT game in 2022!]]></summary></entry><entry><title type="html">How not to screw up your units</title><link href="https://cookncode.com/twincat/2021/09/13/units.html" rel="alternate" type="text/html" title="How not to screw up your units" /><published>2021-09-13T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2021/09/13/units</id><content type="html" xml:base="https://cookncode.com/twincat/2021/09/13/units.html"><![CDATA[<p>Units of values are very important, but also easy to screw up. The F# programming language found a nice solution to this problem. Because units are all over the place in an average PLC project, I started to think about how to replicate this behavior in TwinCAT. Eventually I found a method which gives compiler errors if the wrong units are used.</p>

<ul>
  <li>Example code: <a href="https://github.com/Roald87/TwincatTutorials/tree/main/Units">GitHub repo</a>, <a href="https://downgit.github.io/#/home?url=https://github.com/Roald87/TwincatTutorials/tree/main/Units">direct download</a>.</li>
</ul>

<p>Back when I had my first science classes, the teachers would always emphasize the importance of units. If I forgot them, points were usually subtracted from my final mark. At the time I always found this a bit unreasonable. I was calculating a speed, of course the units would be in km/h!</p>

<p>Over the years I got wiser and started to get the importance of what units are used. I learned that things can go horribly wrong when this is not done. For example, the <a href="https://en.wikipedia.org/wiki/Mars_Climate_Orbiter">Mars Climate Orbiter</a> failed to go into orbit around the red planet, because of a <a href="https://edition.cnn.com/TECH/space/9909/30/mars.metric.02/index.html">mix-up between metric and imperial units</a>.</p>

<p>Now, most of us are not working with hundred million dollar machines which try to orbit a distant planet. But also for less expensive machines on earth it would be nice to not make these mistakes. Below I’ll show the different options I thought of how to prevent unit mix-ups. They are ranked from worst to best, according to my opinion.</p>

<h2 id="unit-checks-in-f">Unit checks in F#</h2>

<p>Before diving into how unit checks can be done in TwinCAT, let me first show you how unit checks are done in F#. In F# a unit can be defined using the following syntax.</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[&lt;</span><span class="nc">Measure</span><span class="p">&gt;]</span> <span class="k">type</span> <span class="nc">Celsius</span>
<span class="p">[&lt;</span><span class="nc">Measure</span><span class="p">&gt;]</span> <span class="k">type</span> <span class="nc">Fahrenheit</span>
</code></pre></div></div>

<p>These units can be used in functions. For example, below a function is defined with <code class="language-plaintext highlighter-rouge">let</code> called <code class="language-plaintext highlighter-rouge">CelsiusToFahrenheit</code>. It has a single input argument called <code class="language-plaintext highlighter-rouge">temperature</code>. Inside the function the conversion takes place using the units defined above. The units are placed between angle brackets <code class="language-plaintext highlighter-rouge">&lt;</code> and <code class="language-plaintext highlighter-rouge">&gt;</code>.</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nc">CelsiusToFahrenheit</span> <span class="n">temperature</span> <span class="p">=</span>
    <span class="n">temperature</span> <span class="p">*</span> <span class="mi">1</span><span class="p">.</span><span class="mi">8</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="o">/</span><span class="nc">Celsius</span><span class="p">&gt;</span> <span class="o">+</span> <span class="mi">32</span><span class="p">.</span><span class="mi">0</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>The unit of <code class="language-plaintext highlighter-rouge">temperature</code> does not need to be supplied, as these will be inferred by the compiler from the units of the variables inside. Now, if we would like to use this function, we also have to use the correct input units.</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;</span> <span class="nc">CelsiusToFahrenheit</span> <span class="mi">21</span><span class="p">.</span><span class="mi">0</span><span class="p">&lt;</span><span class="nc">Celsius</span><span class="o">&gt;;;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">float</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="p">&gt;</span> <span class="p">=</span> <span class="mi">69</span><span class="p">.</span><span class="mi">8</span>
</code></pre></div></div>

<p>If we use the wrong units the compiler will complain.</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;</span> <span class="nc">CelsiusToFahrenheit</span> <span class="mi">62</span><span class="p">.</span><span class="mi">0</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="o">&gt;;;</span>

  <span class="nc">CelsiusToFahrenheit</span> <span class="mi">62</span><span class="p">.</span><span class="mi">0</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="o">&gt;;;</span>
  <span class="o">--------------------^^^^^^^^^^^^^^^^</span>

<span class="n">stdin</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span><span class="mi">21</span><span class="o">):</span> <span class="n">error</span> <span class="nc">FS0001</span><span class="p">:</span> <span class="nc">Type</span> <span class="n">mismatch</span><span class="p">.</span> <span class="nc">Expecting</span> <span class="n">a</span>
    <span class="k">'</span><span class="kt">float</span><span class="p">&lt;</span><span class="nc">Celsius</span><span class="p">&gt;</span><span class="k">'</span>
<span class="n">but</span> <span class="n">given</span> <span class="n">a</span>
    <span class="k">'</span><span class="kt">float</span><span class="p">&lt;</span><span class="nc">Fahrenheit</span><span class="p">&gt;</span><span class="k">'</span>
<span class="nc">The</span> <span class="kt">unit</span> <span class="k">of</span> <span class="n">measure</span> <span class="k">'</span><span class="nc">Celsius'</span> <span class="n">does</span> <span class="k">not</span> <span class="k">match</span> <span class="n">the</span> <span class="kt">unit</span> <span class="k">of</span> <span class="n">measure</span> <span class="k">'</span><span class="nc">Fahrenheit'</span>
</code></pre></div></div>

<p>Likewise, if the unit is omitted we also get a compiler error.</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;</span> <span class="nc">CelsiusToFahrenheit</span> <span class="mi">94</span><span class="p">.</span><span class="mi">0</span><span class="p">;;</span>

  <span class="nc">CelsiusToFahrenheit</span> <span class="mi">94</span><span class="p">.</span><span class="mi">0</span><span class="p">;;</span>
  <span class="o">--------------------^^^^</span>

<span class="n">stdin</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span><span class="mi">21</span><span class="o">):</span> <span class="n">error</span> <span class="nc">FS0001</span><span class="p">:</span> <span class="nc">This</span> <span class="n">expression</span> <span class="n">was</span> <span class="n">expected</span> <span class="k">to</span> <span class="n">have</span> <span class="k">type</span>
    <span class="k">'</span><span class="kt">float</span><span class="p">&lt;</span><span class="nc">Celsius</span><span class="p">&gt;</span><span class="k">'</span>
<span class="n">but</span> <span class="n">here</span> <span class="n">has</span> <span class="k">type</span>
    <span class="k">'</span><span class="n">float'</span>
</code></pre></div></div>

<p>I think this is a very useful feature. If you want to learn more about it, you can read an excellent overview in <a href="https://fsharpforfunandprofit.com/posts/units-of-measure/">F# for Fun and Profit</a>. Now let’s explore how this functionality can be implemented in TwinCAT!</p>

<h2 id="4-putting-units-in-comments">4. Putting units in comments</h2>

<p>Probably the most commonly used method is to put the units of a variable in the comment. For example</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>maximumPressure : REAL; // [mbar]
</code></pre></div></div>

<p>However, it is very hard to spot a mistake as shown by the following example.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
	pressure1 : REAL; // [mbar]
	pressure2 : REAL; // [Torr]
END_VAR

pressure2 := TorrToMbar(pressure1);
</code></pre></div></div>

<p>The pros and cons of this method are:</p>

<p>✔️ There is a unit.</p>

<p>❌ Need to hover over it to see the unit.</p>

<p><a href="/assets/2021-09-13-units/mouse_hover_unit.png"><picture><source srcset="/generated/2021-09-13-units/mouse_hover_unit-269-011cdd5c4.webp 269w" type="image/webp"><source srcset="/generated/2021-09-13-units/mouse_hover_unit-269-65fb960f1.png 269w" type="image/png"><img src="/generated/2021-09-13-units/mouse_hover_unit-269-65fb960f1.png" alt="showing the unit from the comment with a mouse hoover" width="269" height="73"></picture></a></p>

<p>❌ No automatic checks to ensure the right unit is used.</p>

<h2 id="3-using-aliases">3. Using aliases</h2>

<p><a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/3928317323.html&amp;id=">Aliases</a> can be used to define a unit. An alias is just another name for a standard data type. Some standard TwinCAT aliases you might have seen are <code class="language-plaintext highlighter-rouge">T_MaxString</code> = <code class="language-plaintext highlighter-rouge">STRING(255)</code> or <code class="language-plaintext highlighter-rouge">FLOAT</code> = <code class="language-plaintext highlighter-rouge">REAL</code>.</p>

<p>An alias can be created by right clicking on the PLC project and choose <strong>Add &gt; DUT</strong>. Then in the following screen select Alias and give it a name (e.g. mbar) and a datatype (e.g. <code class="language-plaintext highlighter-rouge">REAL</code>).</p>

<p><a href="/assets/2021-09-13-units/create_alias.png"><picture><source srcset="/generated/2021-09-13-units/create_alias-300-523c6ee5a.webp 300w, /generated/2021-09-13-units/create_alias-346-523c6ee5a.webp 346w" type="image/webp"><source srcset="/generated/2021-09-13-units/create_alias-300-97be2080e.png 300w, /generated/2021-09-13-units/create_alias-346-97be2080e.png 346w" type="image/png"><img src="/generated/2021-09-13-units/create_alias-346-97be2080e.png" alt="how to create an alias data type" width="346" height="480"></picture></a></p>

<p>Then it can be used as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
	pressure_si : mbar;
    pressure_imperial : Torr;
END_VAR

pressure_si := TorrToMbarWithAlias(pressure:=pressure_imperial);
</code></pre></div></div>

<p>where <code class="language-plaintext highlighter-rouge">TorrToMbarWithAlias</code> is defined as</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION TorrToMbarWithAlias : mbar
VAR_INPUT
    pressure : Torr;
END_VAR

TorrToMbarWithAlias := pressure * 1.3332236842;
</code></pre></div></div>

<p>At the first glance this looks very similar to the F# approach. However, the aliases are just a different name for the underlying datatype. The compiler only checks that the underlying datatype (<code class="language-plaintext highlighter-rouge">REAL</code> in this case), is correct. So the following would be possible:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pressure_in_torr := TorrToMbarWithAlias(pressure:=pressure_in_mbar);
</code></pre></div></div>

<p>Concluding</p>

<p>✔️ There is a unit.</p>

<p>❌ Need to hover over it to see the unit.</p>

<p>❌ Compiler doesn’t complain if the wrong alias is used.</p>

<h2 id="2-add-unit-to-the-variable-name">2. Add unit to the variable name</h2>

<p>Another option would be to append the unit to the variable name:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>maximumPressure_mbar : REAL;
</code></pre></div></div>

<p>This method also doesn’t have any automatic check to prevent unit mix-ups. But the following mistake is harder to make, because the input variable’s unit doesn’t match the argument name one.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
    pressure1_mbar : REAL;
    pressure2_Torr : REAL;
END_VAR

pressure2_Torr := TorrToMbarWithUnits(pressure_Torr:=pressure1_mbar);
</code></pre></div></div>

<p>✔️ There is a unit.</p>

<p>✔️ No need to hover over it to see the unit.</p>

<p>❌ No automatic checks to ensure the right unit is used.</p>

<h2 id="1-using-structs">1. Using structs</h2>

<p>We’re making progress, but the compiler is not helping us yet. In order for the compiler to help us, STRUCTs can be used. You can add a STRUCT by right clicking on your PLC project and selecting <strong>Add &gt; DUT</strong>.</p>

<p><a href="/assets/2021-09-13-units/create_struct.png"><picture><source srcset="/generated/2021-09-13-units/create_struct-300-a03d6e686.webp 300w, /generated/2021-09-13-units/create_struct-346-a03d6e686.webp 346w" type="image/webp"><source srcset="/generated/2021-09-13-units/create_struct-300-1811523bd.png 300w, /generated/2021-09-13-units/create_struct-346-1811523bd.png 346w" type="image/png"><img src="/generated/2021-09-13-units/create_struct-346-1811523bd.png" alt="how to create a struct" width="346" height="480"></picture></a></p>

<p>And then define the STRUCTs as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TYPE Pascal :
STRUCT
    _ : REAL;
END_STRUCT
END_TYPE
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TYPE PoundsPerSquareInch :
STRUCT
     _ : REAL;
END_STRUCT
END_TYPE
</code></pre></div></div>

<p>We then add a conversion function.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION PoundsPerSquareInchToPascal : Pascal
VAR_INPUT
    pressure : PoundsPerSquareInch;
END_VAR

PoundsPerSquareInchToPascal._ := pressure._ * 6895;
</code></pre></div></div>

<p>If we try to run this function with the wrong units:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
    pressure_eu : Pascal;
    pressure_us : PoundsPerSquareInch;
END_VAR

pressure_us := PoundsPerSquareInchToPascal(pressure:=pressure_eu);
</code></pre></div></div>

<p>the compiler will complain. 🙌</p>

<p><a href="/assets/2021-09-13-units/convert_type_error.png"><picture><source srcset="/generated/2021-09-13-units/convert_type_error-300-5675a09ad.webp 300w, /generated/2021-09-13-units/convert_type_error-420-5675a09ad.webp 420w" type="image/webp"><source srcset="/generated/2021-09-13-units/convert_type_error-300-626257858.png 300w, /generated/2021-09-13-units/convert_type_error-420-626257858.png 420w" type="image/png"><img src="/generated/2021-09-13-units/convert_type_error-420-626257858.png" alt="compiler error from using the wrong struct" width="420" height="42"></picture></a></p>

<p>✔️ There is a unit.</p>

<p>❌ Need to hover over it to see the unit, but this is a minor issue since the compiler will check it for us.</p>

<p>✔️ Compiler complains if the wrong type is used.</p>

<p>❌ The interface is not very convenient, since we have to append <code class="language-plaintext highlighter-rouge">._</code> each time we want to access the value.</p>

<h3 id="adding-unit-string-as-payload">Adding unit string as payload</h3>

<p>Using structs gives us a major advantage since the compiler will help us. However, it feels a bit excessive to define a whole struct for this and then we have to use the <code class="language-plaintext highlighter-rouge">._</code> to assign or use the variable inside. We can ease the pain a bit by adding some useful information to the struct. For example, we could add the units to the struct in string format. This string could then be used in the HMI for example.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TYPE mmHg :
STRUCT
    _ : REAL;
    unit : STRING := 'mmHg';
END_STRUCT
END_TYPE
</code></pre></div></div>

<p><a href="/assets/2021-09-13-units/struct_with_string_unit.png"><picture><source srcset="/generated/2021-09-13-units/struct_with_string_unit-300-9b2eaff5f.webp 300w, /generated/2021-09-13-units/struct_with_string_unit-439-9b2eaff5f.webp 439w" type="image/webp"><source srcset="/generated/2021-09-13-units/struct_with_string_unit-300-43582513b.png 300w, /generated/2021-09-13-units/struct_with_string_unit-439-43582513b.png 439w" type="image/png"><img src="/generated/2021-09-13-units/struct_with_string_unit-439-43582513b.png" alt="a struct with a unit as a string" width="439" height="64"></picture></a></p>

<h3 id="adding-io-signal-status">Adding IO signal status</h3>

<p>Another option could be to add some information about the state of the input signal. Terminals usually have a WcState variable. This variable shows if the terminal cyclically exchanges data with the master and does this without errors. Furthermore, some input channels also have a state, which can show if there is an open circuit, short circuit or some other irregularity.</p>

<p>To capture the terminal status we make a new struct.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TYPE TerminalStatus :
STRUCT
    WorkingCounterError : BOOL;
    IOError : BOOL;
END_STRUCT
END_TYPE
</code></pre></div></div>

<p>Then whenever we define a new unit, we extend the struct above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TYPE Celsius EXTENDS TerminalStatus :
STRUCT
    _ : REAL;
END_STRUCT
END_TYPE
</code></pre></div></div>

<p>In order to check whether a signal is valid, we can define a new function. The function uses the general <code class="language-plaintext highlighter-rouge">TerminalStatus</code> as its input type. That way this function can be called with extended structs, such as <code class="language-plaintext highlighter-rouge">Celsius</code>, as I <a href="https://cookncode.com/twincat/2021/04/20/overloading-with-extended-structs.html">wrote about in an earlier article</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION IsValid : BOOL
VAR_INPUT
    status : TerminalStatus;
END_VAR

IsValid := NOT (status.IOError OR status.WorkingCounterError);
</code></pre></div></div>

<p>Finally we define a conversion function, where we only convert the temperature if the signal state is ok.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION FahrenheitToCelsius : Celsius
VAR_INPUT
    temperature : Fahrenheit;
END_VAR

FahrenheitToCelsius.IOError := temperature.IOError;
FahrenheitToCelsius.WorkingCounterError := temperature.WorkingCounterError;
IF IsValid(temperature) THEN
    FahrenheitToCelsius._ := (temperature._ - 32) /  1.8;
END_IF
</code></pre></div></div>

<p>After activating this code we can see it in action.</p>

<p><img src="/assets/2021-09-13-units/toggle_valid_signal.gif" alt="toggling of the validity of the input signal" /></p>

<h2 id="conclusions">Conclusions</h2>

<p>In the end we came quite close to the F# unit checks implementation. Although the intermediate conversions, such as the 32 in <code class="language-plaintext highlighter-rouge">FahrenheitToCelsius</code>, are not checked by the compiler as in F#. But these intermediate conversions can be checked relatively easy with unit tests. What is checked with the method above are the in and outputs of the function. I would argue this is the most important part, since they usually need <a href="https://cookncode.com/twincat/2021/08/17/tc-simulation.html">integration tests</a>. Finally the interface of a custom struct is not the most convenient, but I showed some examples how you could soften the pain by adding some useful information to the unit structs.</p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[Units of values are very important, but also easy to screw up. The F# programming language found a nice solution to this problem. Because units are all over the place in an average PLC project, I started to think about how to replicate this behavior in TwinCAT. Eventually I found a method which gives compiler errors if the wrong units are used.]]></summary></entry><entry><title type="html">Machine simulation with a digital twin</title><link href="https://cookncode.com/twincat/2021/08/17/tc-simulation.html" rel="alternate" type="text/html" title="Machine simulation with a digital twin" /><published>2021-08-17T00:00:00+00:00</published><updated>2026-04-14T18:42:22+00:00</updated><id>https://cookncode.com/twincat/2021/08/17/tc-simulation</id><content type="html" xml:base="https://cookncode.com/twincat/2021/08/17/tc-simulation.html"><![CDATA[<p>When you’re developing PLC code it can be difficult to test the behavior of the whole code, due to all the disconnected in and outputs. A colleague of mine found a nice solution how you can create a digital twin of the real machine. The digital twin then simulates the behavior of the real one. In this article I’ll show you how you can do this with a simple oven project.</p>

<ul>
  <li>Example code: <a href="https://github.com/Roald87/TwincatTutorials/tree/main/DigitalTwin">GitHub repo</a>, <a href="https://downgit.github.io/#/home?url=https://github.com/Roald87/TwincatTutorials/tree/main/DigitalTwin">direct download</a>.</li>
</ul>

<h2 id="different-types-of-tests">Different types of tests</h2>

<p>There are different levels at which you can test your code, as shown in the image below. In this article I will focus on how you can do integration tests without the need for a real machine.</p>

<p><a href="/assets/2021-08-17-tc-simulation/ThePyramidOfTests.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/ThePyramidOfTests-300-56ea66c32.webp 300w, /generated/2021-08-17-tc-simulation/ThePyramidOfTests-500-56ea66c32.webp 500w, /generated/2021-08-17-tc-simulation/ThePyramidOfTests-720-56ea66c32.webp 720w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/ThePyramidOfTests-300-a08b7a6b5.png 300w, /generated/2021-08-17-tc-simulation/ThePyramidOfTests-500-a08b7a6b5.png 500w, /generated/2021-08-17-tc-simulation/ThePyramidOfTests-720-a08b7a6b5.png 720w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/ThePyramidOfTests-800-a08b7a6b5.png" alt="alltwincat.com pyramid of tests" width="1920" height="1080"></picture></a></p>

<p><em>Image courtesy of AllTwinCAT.com</em></p>

<h3 id="unit-tests">Unit tests</h3>

<p>On the bottom of the pyramid are the unit tests. With unit tests you test individual functions and function blocks. For TwinCAT there are several options if you want to do this: <a href="https://alltwincat.com/2021/02/16/unit-testing-in-the-world-of-industrial-automation/">TcProber</a>, <a href="https://tcunit.org/">TcUnit.org</a> and <a href="https://github.com/StefanHenneken/Blog-2017-08-IEC61131-UnitTests-TcUnit">TcUnit</a>. You can learn more about the first two libraries in this article on <a href="https://alltwincat.com/2021/02/16/unit-testing-in-the-world-of-industrial-automation/">AllTwinCAT</a> and about the last one on <a href="https://stefanhenneken.net/2018/01/24/iec-61131-3-unit-tests/">Stefan Henneken’s blog</a>.</p>

<h3 id="integration-tests">Integration tests</h3>

<p>After testing the individual units, you want to make sure that once you glue several units together they work as intended. This is called <a href="https://en.wikipedia.org/wiki/Integration_testing">integration testing</a>. For this there aren’t that many well developed options that I know of.</p>

<p>One option is that you create an actual 3D model of your machine and connect that to your PLC as shown on the <a href="https://dronefactory.co.uk/">WJB Automation Blog</a>. By using the 3D model you can easily check if parts are moving as intended and that there are no collisions. However, if you have a machine with less or no moving parts, this method is not an option. Furthermore it is harder to automate these tests further down the line.</p>

<p>The second option is to manually force different in/outputs in your program and verify that everything works as intended. This option is, of course, vey time consuming and not automated. And if you’re like me, than you didn’t become a software engineer to do things manually 😁.</p>

<p>The third and last option is to make use of Beckhoff’s <a href="https://www.beckhoff.com/en-en/products/automation/twincat/texxxx-twincat-3-engineering/te1111.html">EtherCAT Simulation module</a>. This a quite extensive module (<a href="https://infosys.beckhoff.com/content/1033/te1111_ethercat_simulation/9007206134823819.html?id=6382247128242284904">InfoSys</a>):</p>

<blockquote>
  <p>The TE1111 TwinCAT EtherCAT Simulation function simulates an EtherCAT segment. An already created I/O configuration of the real plant is exported and can be imported into a second system as an ‘EtherCAT Simulation’ device. A mirrored process image is thus available on this system that can be linked with corresponding TwinCAT modules (e.g. written in the IEC 61131-3 languages or generated from Matlab®/Simulink®). The desired behavior of the machine must be implemented with sufficient accuracy in these modules.</p>
</blockquote>

<p>When my colleague tested this, he didn’t quite get it to work for our machine. The simulation was having trouble simulating all the different terminals he thought. Long story short, that is why he decided to make a simpler version completely from scratch. Another small advantage, especially if you want to try this at home, is that you do not need a license to do this.</p>

<h2 id="digital-twin">Digital twin?</h2>

<p>What is a digital twin and why would you want one? Lets start with the why first. When you have your PLC project it has numerous in and outputs as visualized below.</p>

<p><a href="/assets/2021-08-17-tc-simulation/plc_IO.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/plc_IO-293-212ce21fb.webp 293w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/plc_IO-293-93ca9b083.png 293w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/plc_IO-293-93ca9b083.png" alt="PLC project IO connected to the void" width="293" height="246"></picture></a></p>

<p>If you would like to do integration tests, or test your HMI, then it is often very difficult because the IOs are not connected. For example, you want to test some conveyer belt functionality, but you can’t turn it on because some safety latch is not closed.</p>

<p>That is where a <a href="https://en.wikipedia.org/wiki/Digital_twin">digital twin</a> comes in. A digital twin (or a TwinTwinCAT project? 😬) is a digital or virtual representation of your real machine. By making a digital twin we can mirror our IO’s and connect them to the original PLC project as shown here.</p>

<p><a href="/assets/2021-08-17-tc-simulation/plc_IO_with_twin.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/plc_IO_with_twin-300-7cbc499a4.webp 300w, /generated/2021-08-17-tc-simulation/plc_IO_with_twin-470-7cbc499a4.webp 470w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/plc_IO_with_twin-300-e6df86a74.png 300w, /generated/2021-08-17-tc-simulation/plc_IO_with_twin-470-e6df86a74.png 470w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/plc_IO_with_twin-470-e6df86a74.png" alt="PLC project IO connected to a digital twin" width="470" height="256"></picture></a></p>

<p>The twin can be made as detailed as desired. Components can be simulated just enough such that things can turn things on and off. Or individual terminals can be simulated, such that the failure of a single IO can be tested.</p>

<p>Once you have a working TwinTwinCAT project, it can be used to do (automated) integration tests. In this post I will not go into the automated tests. I will show you how to create a simple digital twin of an oven project. Perhaps I’ll do the automated tests in a future post.</p>

<h2 id="building-a-digital-twin">Building a digital twin</h2>

<p>Now I will guide you through how to build the digital twin project. I will only go into details on the relevant parts which are needed to get the twin working. The exact implementation of the function blocks you can see for yourself if you download the whole project’s <a href="https://github.com/Roald87/TwincatTutorials/tree/main/DigitalTwin">GitHub</a> repo or the <a href="https://downgit.github.io/#/home?url=https://github.com/Roald87/TwincatTutorials/tree/main/DigitalTwin">direct download</a>.</p>

<p>Below you see a schematic representation of the PLC project. The main part is the <code class="language-plaintext highlighter-rouge">Oven</code> function block. It takes a command from the HMI which can change its state. It also sends a command and receives a state from the <code class="language-plaintext highlighter-rouge">Relay</code> which will turn on the power of the heater inside oven. The oven sets the heater current based on the set temperature. Finally it receives feedback from the thermocouple about the measured temperature.</p>

<p><a href="/assets/2021-08-17-tc-simulation/plc_schematic.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/plc_schematic-300-24b66eb3b.webp 300w, /generated/2021-08-17-tc-simulation/plc_schematic-500-24b66eb3b.webp 500w, /generated/2021-08-17-tc-simulation/plc_schematic-720-24b66eb3b.webp 720w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/plc_schematic-300-989ac7999.png 300w, /generated/2021-08-17-tc-simulation/plc_schematic-500-989ac7999.png 500w, /generated/2021-08-17-tc-simulation/plc_schematic-720-989ac7999.png 720w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/plc_schematic-800-989ac7999.png" width="821" height="354"></picture></a></p>

<h3 id="creating-a-stand-alone-plc--hmi-project">Creating a stand-alone PLC &amp; HMI project</h3>

<p>I’ve implemented the above functionality in a <a href="https://cookncode.com/twincat/2020/01/29/standalone-plc-projects.html">stand-alone PLC project</a>. A stand-alone project has the advantage that it can be imported both into a digital twin TwinCAT configuration project as well as a real machine TwinCAT configuration. I recommend that you open the <code class="language-plaintext highlighter-rouge">Oven.sln</code> file located in <code class="language-plaintext highlighter-rouge">DigitalTwin/Oven</code> for this part. If you opt for this, you can skip to <a href="#creating-the-digital-twin-project">the next section</a>.</p>

<p>To create the stand-alone PLC project from scratch you can:</p>

<ol>
  <li>Open Visual Studio and Go to <strong>File &gt; New &gt; Project</strong></li>
  <li>Select <strong>TwinCAT PLC &gt; TwinCAT PLC Project</strong></li>
  <li>Give it a name and location and click <strong>OK</strong></li>
  <li>Then in the next window under <strong>PLC Templates</strong> select <strong>Standard PLC project</strong></li>
  <li>Give it a name and click on <strong>Add</strong>.</li>
  <li>Add the POUs as desired.</li>
</ol>

<p>Next we’ll add a HMI project to the stand-alone solution to get some visual feedback. To add the one I prepared, go to <strong>File &gt; Add &gt; Existing project</strong> and add the <code class="language-plaintext highlighter-rouge">HmiProject.hmiproj</code> found in <code class="language-plaintext highlighter-rouge">DigitalTwin\Oven\HmiProject</code>.</p>

<h3 id="creating-the-digital-twin-project">Creating the digital twin project</h3>

<p>Now we are going to create the TwinTwinCAT project. Open a new Visual Studio window to create a new solution and go to <strong>File &gt; New &gt; Project</strong>. Choose <strong>TwinCAT Projects &gt; TwinCAT XAE Project (XML format)</strong>.</p>

<p>Add the oven stand-alone plc project by right clicking on <strong>PLC &gt; Add Existing Item..</strong>. Then navigate to the Oven PLC project. You can either add the <code class="language-plaintext highlighter-rouge">PLC.tmc</code> file or the <code class="language-plaintext highlighter-rouge">PLC.plcproj</code>. Note that the <code class="language-plaintext highlighter-rouge">PLC.tmc</code> only appears after the first successful build of the stand-alone project.</p>

<p>The advantage if you add the <code class="language-plaintext highlighter-rouge">.plcproj</code> file, is that you can directly edit the function blocks of the stand-alone project (the one in <code class="language-plaintext highlighter-rouge">DigitalTwin/Oven/Oven/Oven.tsproj</code>) from the digital twin project (the one in <code class="language-plaintext highlighter-rouge">DigitalTwin/OvenConfig/OvenConfig/OvenConfig.tsproj</code>). A disadvantage is that if you have the stand-alone project open, it will ask you to reload files every time you make a change. If you choose to go with the <code class="language-plaintext highlighter-rouge">.tmc</code> file option, you get neither the advantage nor the disadvantage.</p>

<p>If you select the <code class="language-plaintext highlighter-rouge">.tmc</code> file, make sure to select the “Auto Reload TMI/TMC” option. This option can be found under <strong>PLC Instance &gt; Object</strong>. This will make sure that whenever the <code class="language-plaintext highlighter-rouge">.tmc</code> file of the stand-alone project changes, it will automatically be reloaded into the digital twin project. I used the <code class="language-plaintext highlighter-rouge">.tmc</code> file for this tutorial.</p>

<p><a href="/assets/2021-08-17-tc-simulation/auto_reload_tmc.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/auto_reload_tmc-300-555181ef5.webp 300w, /generated/2021-08-17-tc-simulation/auto_reload_tmc-500-555181ef5.webp 500w, /generated/2021-08-17-tc-simulation/auto_reload_tmc-720-555181ef5.webp 720w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/auto_reload_tmc-300-7e1a9b46a.png 300w, /generated/2021-08-17-tc-simulation/auto_reload_tmc-500-7e1a9b46a.png 500w, /generated/2021-08-17-tc-simulation/auto_reload_tmc-720-7e1a9b46a.png 720w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/auto_reload_tmc-755-7e1a9b46a.png" alt="image-20210813081517593" width="755" height="257"></picture></a></p>

<p>If you’ve selected the <code class="language-plaintext highlighter-rouge">.plcproj</code> file, then make sure to select “Use original project location”. With this option you can directly edit the PLC project from the digital twin project.</p>

<p><a href="/assets/2021-08-17-tc-simulation/add_existing_project.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/add_existing_project-300-35d0e0ac0.webp 300w, /generated/2021-08-17-tc-simulation/add_existing_project-358-35d0e0ac0.webp 358w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/add_existing_project-300-577359f3f.png 300w, /generated/2021-08-17-tc-simulation/add_existing_project-358-577359f3f.png 358w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/add_existing_project-358-577359f3f.png" alt="image-20210813071116565" width="358" height="185"></picture></a></p>

<p>Next, add a new PLC project which will become our digital twin PLC project. Right click on <strong>PLC &gt; Add New Item…</strong> and choose <strong>PLC template &gt; Standard PLC project</strong>. Give it a name, e.g. SimulationPlc.</p>

<p>In the SimulationPlc project, rename the PlcTask to SimulationTask. This is to make a clear distinction between the PlcTask from the Oven project and the Simulation project.</p>

<p>Then make a new task and call it SimTask.</p>

<p><a href="/assets/2021-08-17-tc-simulation/add_sim_task.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/add_sim_task-300-17e63ae3c.webp 300w, /generated/2021-08-17-tc-simulation/add_sim_task-443-17e63ae3c.webp 443w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/add_sim_task-300-262cb2c73.png 300w, /generated/2021-08-17-tc-simulation/add_sim_task-443-262cb2c73.png 443w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/add_sim_task-443-262cb2c73.png" alt="image-20210813073830232" width="443" height="274"></picture></a></p>

<p>Next right click on SimulationTask and select <strong>Assign to task</strong></p>

<p><a href="/assets/2021-08-17-tc-simulation/assign_to_task.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/assign_to_task-281-f235b4e02.webp 281w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/assign_to_task-281-be6faffbe.png 281w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/assign_to_task-281-be6faffbe.png" alt="image-20210813074059107" width="281" height="113"></picture></a></p>

<p>Then select the <strong>SimTask</strong> as the new task where SimulationTask will be assigned to.</p>

<p><a href="/assets/2021-08-17-tc-simulation/assign_to_simtask.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/assign_to_simtask-237-59f36fb94.webp 237w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/assign_to_simtask-237-03f035998.png 237w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/assign_to_simtask-237-03f035998.png" alt="image-20210813074201087" width="237" height="256"></picture></a></p>

<h3 id="adding-digital-twin-objects">Adding digital twin objects</h3>

<p>In this section I will show you how you can create the digital twins of the <code class="language-plaintext highlighter-rouge">Relay</code> and <code class="language-plaintext highlighter-rouge">Oven</code> function blocks.</p>

<p><a href="/assets/2021-08-17-tc-simulation/plc_and_sim_schematic.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/plc_and_sim_schematic-300-37294ac57.webp 300w, /generated/2021-08-17-tc-simulation/plc_and_sim_schematic-500-37294ac57.webp 500w, /generated/2021-08-17-tc-simulation/plc_and_sim_schematic-720-37294ac57.webp 720w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/plc_and_sim_schematic-300-f2d7f4827.png 300w, /generated/2021-08-17-tc-simulation/plc_and_sim_schematic-500-f2d7f4827.png 500w, /generated/2021-08-17-tc-simulation/plc_and_sim_schematic-720-f2d7f4827.png 720w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/plc_and_sim_schematic-800-f2d7f4827.png" width="1032" height="360"></picture></a></p>

<h4 id="mirroring-the-relay">Mirroring the relay</h4>

<p>The PLC project has a function block <code class="language-plaintext highlighter-rouge">Relay</code> which represents a relay. The relay has one output <code class="language-plaintext highlighter-rouge">turnOnRelay AT %Q* : BOOL;</code> which is set to <code class="language-plaintext highlighter-rouge">TRUE</code> if the relay needs to turn on. It then watches for the feedback of the relay if it actually turned on with <code class="language-plaintext highlighter-rouge">relayOnFeedback AT %I* : BOOL;</code>. If the <code class="language-plaintext highlighter-rouge">relayOnFeedback</code> is set to <code class="language-plaintext highlighter-rouge">TRUE</code>, <code class="language-plaintext highlighter-rouge">Relay</code> will switch to the on state.</p>

<p>Without a simulation project, the feedback will never go to true, so the relay will always be in state off or turning on. So that is why we get this:</p>

<p><img src="/assets/2021-08-17-tc-simulation/no_sim_relay.gif" alt="" /></p>

<p>Go get the correct relay behavior we will create a mirror object of <code class="language-plaintext highlighter-rouge">Relay</code> called <code class="language-plaintext highlighter-rouge">SimRelay</code>. <code class="language-plaintext highlighter-rouge">SimRelay</code> will have the mirrored I/O as <code class="language-plaintext highlighter-rouge">Relay</code> in the PLC project. I.e. <code class="language-plaintext highlighter-rouge">%I*</code> becomes <code class="language-plaintext highlighter-rouge">%Q*</code> and vice versa:</p>

<table>
  <thead>
    <tr>
      <th><code class="language-plaintext highlighter-rouge">Relay</code></th>
      <th><code class="language-plaintext highlighter-rouge">SimRelay</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">relayOnFeedback AT %I* : BOOL;</code></td>
      <td>➔ <code class="language-plaintext highlighter-rouge">relayOnFeedback AT %Q* : BOOL;</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">turnOnRelay AT %Q* : BOOL</code></td>
      <td>➔ <code class="language-plaintext highlighter-rouge">turnOnRelay AT %I* : BOOL;</code></td>
    </tr>
  </tbody>
</table>

<p>For the implementation of <code class="language-plaintext highlighter-rouge">SimRelay</code> I added some delays before the feedback is set, in order to clearly see the state transitions in the HMI. The complete codes is then as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION_BLOCK SimRelay
VAR
    relayOnFeedback AT %Q* : BOOL;
    turnOnRelay AT %I* : BOOL;
    relayOnDelay : TON := (PT:=delay);
    relayOffDelay : TOF := (PT:=delay);
END_VAR
VAR CONSTANT
    delay : TIME := T#1S;
END_VAR

relayOnDelay(IN:=turnOnRelay);
IF relayOnDelay.Q THEN
    relayOnFeedback := TRUE;
END_IF

relayOffDelay(IN:=turnOnRelay);
IF NOT relayOffDelay.Q THEN
    relayOnFeedback := FALSE;
END_IF
</code></pre></div></div>

<p>Next we create an instance of the <code class="language-plaintext highlighter-rouge">SimRelay</code> and call it every cycle in the <code class="language-plaintext highlighter-rouge">MAIN</code> program of our simulation project.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROGRAM MAIN
VAR
    simRelay : SimRelay;
END_VAR

simRelay();
</code></pre></div></div>

<p>Now we can link the instance of <code class="language-plaintext highlighter-rouge">Relay</code> in the PLC project with <code class="language-plaintext highlighter-rouge">SimRelay</code> of the simulation project. To do so, double click on SimulationTask Inputs and right click on <code class="language-plaintext highlighter-rouge">MAIN.simRelay.turnOnRelay</code> and select <strong>Change Link…</strong>.</p>

<p><a href="/assets/2021-08-17-tc-simulation/change_link.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/change_link-300-1ab6b6e53.webp 300w, /generated/2021-08-17-tc-simulation/change_link-403-1ab6b6e53.webp 403w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/change_link-300-15f1a0f82.png 300w, /generated/2021-08-17-tc-simulation/change_link-403-15f1a0f82.png 403w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/change_link-403-15f1a0f82.png" alt="image-20210813143930619" width="403" height="125"></picture></a></p>

<p>Then select the relay output from the PLC project.</p>

<p><a href="/assets/2021-08-17-tc-simulation/relay-turnOnRelay.png"><picture><source srcset="/generated/2021-08-17-tc-simulation/relay-turnOnRelay-300-7339ea155.webp 300w, /generated/2021-08-17-tc-simulation/relay-turnOnRelay-420-7339ea155.webp 420w" type="image/webp"><source srcset="/generated/2021-08-17-tc-simulation/relay-turnOnRelay-300-2495ee332.png 300w, /generated/2021-08-17-tc-simulation/relay-turnOnRelay-420-2495ee332.png 420w" type="image/png"><img src="/generated/2021-08-17-tc-simulation/relay-turnOnRelay-420-2495ee332.png" alt="image-20210813144202680" width="420" height="151"></picture></a></p>

<p>Do the same with the SimulationTask Outputs, where you can link the <code class="language-plaintext highlighter-rouge">MAIN.SimRelay.relayOnFeedback</code> to <code class="language-plaintext highlighter-rouge">MAIN.Relay.relayOnFeedback</code>. Then you reactivate the configuration and you should see the oven go from on to off via the turning on and off states.</p>

<p><img src="/assets/2021-08-17-tc-simulation/sim_relay.gif" alt="" /></p>

<h4 id="mirroring-the-oven">Mirroring the oven</h4>

<p>Now the oven can be turned on, but it doesn’t heat up if a temperature is set. In order to make this work we need to create <code class="language-plaintext highlighter-rouge">SimOven</code>. In this function block I again take the mirrored IO of the <code class="language-plaintext highlighter-rouge">Oven</code> one. Then to simulate gradual heat-up, I’ve used some <a href="https://en.wikipedia.org/wiki/Exponential_smoothing">exponential smoothing</a>. The filter takes 99.5 % of the temperature of the previous cycle and adds 0.5% of the new temperature. The temperature is calculated by the current squared as per <a href="https://en.wikipedia.org/wiki/Joule_effect">Joule’s first law</a>. The limit is added to prevent the temperature from going below room temperature. The complete code is as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FUNCTION_BLOCK SimOven
VAR
    thermoCouple_Celcius AT %Q* : REAL;
    heaterCurrent_Ampere AT %I* : REAL;
END_VAR
VAR
    previousTemperature : REAL;
    filterParameter : REAL := 0.995;
END_VAR

thermoCouple_Celcius := previousTemperature * filterParameter
    + LIMIT(20, TO_REAL(EXPT(heaterCurrent_Ampere, 2)), 1E5) * (1 - filterParameter);
previousTemperature := thermoCouple_Celcius;
</code></pre></div></div>

<p>After making an instance of <code class="language-plaintext highlighter-rouge">SimOven</code> in <code class="language-plaintext highlighter-rouge">MAIN</code> and linking the IOs of the simulation project with the PLC project we get a working oven! 🎉</p>

<p><img src="/assets/2021-08-17-tc-simulation/heating_oven.gif" alt="" /></p>

<p>Discuss: <a href="https://www.reddit.com/r/TwinCat/comments/p6ar7t/machine_simulation_with_a_digital_twin/">r/TwinCAT</a>.</p>]]></content><author><name></name></author><category term="twincat" /><summary type="html"><![CDATA[When you’re developing PLC code it can be difficult to test the behavior of the whole code, due to all the disconnected in and outputs. A colleague of mine found a nice solution how you can create a digital twin of the real machine. The digital twin then simulates the behavior of the real one. In this article I’ll show you how you can do this with a simple oven project.]]></summary></entry></feed>