Jekyll2025-08-01T14:27:08+00:00https://cookncode.com/feed.xmlCook & CodePersonal projects and ideas.How to structure a story2025-08-01T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/writing/2025/08/01/story-structuresI often visualize story structure with two vertical lines. The top is the beginning, and the bottom is the end. The distance between the lines reflects how detailed the story is at that point. Visualizing it this way helps me remember different structures. Here, I’ll discuss the following: the hourglass, the Klein bottle, and the Minto pyramid.

The hourglass

The hourglass structure starts wide to capture a broad audience. As the story continues, it becomes increasingly detailed. Toward the end, the lines widen again as the storyteller makes broader statements and places the story in a larger context.

A typical example of this structure is in academic publications or presentations. Publications often have a double hourglass structure: one for the abstract and one for the main text.

abstract main text

For the abstract, this is beautifully illustrated by this template abstract by Fabrizio Gilardi from ETH Zürich:

Everyone agrees that this issue is really important. But we do not know much about this specific question, although it matters a great deal, for these reasons. We approach the problem from this perspective. Our research design focuses on these cases and relies on these data, which we analyze using this method. Results show what we have learned about the question. They have these broader implications.

Everyone agrees this issue is important.There are many unknowns, but they are vital.We approach the problem from this angle.We uses these data, analyzed with this method.Results show what we learned.They have broader implications.

Similarly, the structure of the main article follows the hourglass structure: introduction, methods, results, discussion, conclusion.

introductionmethodsresultsdiscussionconclusions

The Klein bottle

The Klein bottle is a curious shape that has only a single surface.

By Tttrung - Own work, CC BY-SA 3.0, Link

A Klein bottle story starts wide, often with an anecdote or trivia, to capture a large audience. Then, it goes into increasing detail. At the end, the storyteller returns to the opening anecdote.

introduction with anecdotegoing into more detailcoming back to the anecdote

The Minto pyramid

This structure was developed by Barbara Minto, hence the name. It starts with the main point, conclusion, or answer. The main point is then supported by arguments, and finally by data and facts.

main pointsupporting argumentssupporting data and facts ]]>
State of the Industry: Hannover Messe 20232023-05-02T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat%20plc%20simatic/2023/05/02/hannover-2023In this post, I summarize my thoughts after visiting the Hannover Messe in 2023. I focus on developments in PLC software. I found that the industry is focusing more on software and developing tools catered to software developers.

I wrote this article back in 2023 but forgot to post it. Better late than never.

Before I start: where do you believe we currently are on the Industry X.0 scale?

1.0 steam 2.0 electrification 3.0 digitization 4.0 smart automation 5.0 human–machine connectivity

Historical context

Before diving into the current state, I want to explain how we got here.

It started with the Industrial Revolution when we used steam to power machines—Industry 1.0. Then came electrification: Industry 2.0.

With electrical control, relays could implement some logic control in machines. For example: if a box of height 1 arrives here, activate this piston; if the height is larger, activate a second piston. This enabled more complex processes, but changes were cumbersome, and the relay cabinets were large, as seen in the image.

Cue the digital revolution—Industry 3.0. Digitizing the logic allowed for more complex, flexible control. This gave us programmable logic controllers, or PLCs.

PLC programming and tooling were made by and for hardware engineers. That’s why ladder logic resembles a relay schematic. There was never a real separation between hardware and software roles, unlike in general IT.

Until now.

The rise of software people

Over the last few years, I’ve noticed more people with traditional IT backgrounds—including myself—entering the field of industrial automation. These people are used to different tools: command-line interfaces, plain text files, and reusing other people’s code.

For a long time, these things either didn’t exist or barely did. But that is changing. Some new players have entered the space, and some old ones have learned new tricks. I’ll introduce the new ones first, then the old.

New kids on the block

Controllino

Controllino made an Arduino-based PLC. It’s quite affordable (€100–€300), though limited in functionality. Depending on the model, you get a fixed number of digital and analog I/O. Currently, there’s no option to extend it.

The most interesting part is the software. Since it’s Arduino-based, you can use existing Arduino sketches. They also support Python integration for non-time-critical tasks.

It’s a great platform for quick prototyping, especially since many developers are more familiar with Arduino code than with IEC-based languages.

Revolution Pi

Another new player, this one is Raspberry Pi-based. It offers more compute power and supports up to ten add-on terminals. Along with digital and analog I/O, it also supports Profinet, Profibus, and Ethernet terminals. Prices are higher than for the Arduino option.

The Pi PLC can be programmed using traditional PLC languages, or with C and Python.

Software Defined Automation

This company doesn’t produce PLCs—it’s a SaaS provider. Their product focuses on PLCops: managing PLCs and code history across vendors, factories, and machines.

It’s interesting to see software-only companies entering the PLC space.

Old players

Simatic AX

Siemens surprised me most with its new Simatic AX framework. It’s a completely new approach to PLC programming, compared to their previous TIA Portal solution.

The editor is VS Code–based. Code is saved in plain text, there’s a CLI, a package manager, unit tests, an autoformatter, and configuration is done in YAML files!

Amazing.

All the wishes Jakob and Peter mentioned in their TwinCAT 4 wish list are implemented—and more.

Currently, Simatic AX is in limited beta and can be used to write libraries for import into TIA Portal. It sounds very promising, and I think AX will quickly replace TIA Portal.

ctrlX Automation

Rexroth has built a new PLC platform based on a real-time version of Ubuntu. It even runs on other hardware (though without hard real-time guarantees).

I haven’t used it myself, but the features sound great. For example, the PLC runs a web server, allowing configuration directly from a PC—no software installation required.

You can also write real-time code in C and integrate it into your PLC logic.

Finally, all PLC variables are exposed over a REST API. This allows reading/writing from any programming language.

One great demo featured a Jupyter notebook executing commands like axis.goto(1.1, 2.1, 3.5) on demand. This makes it easy to experiment with actuators.

TwinCAT

From Beckhoff, I didn’t see many promising software developments. They launched a ChatGPT API for TwinCAT, which is ironic considering these models rely on open-source code—something mostly absent for Structured Text. They may have retrained it on internal code, though.

Wrapping up

The mindset around PLC software development is shifting. Not only are users changing—suppliers are too. It’s good to see vendors offer native interfaces and support general-purpose languages like Python and C. More code is being shared on GitHub. And Arduino- and Raspberry Pi–based products are introducing fresh perspectives.

I estimate we’re currently at Industry 3.2. But with these tools on the horizon, we may finally reach the long-hyped Industry 4.0.

]]>
Up your falafel game with this easy hack2023-03-05T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/cooking/2023/03/05/falafelFalafel is delicious if you make it from scratch, but it can be quite time-consuming. The ready-made falafel or the dried mix you can buy is mediocre and doesn’t hold a candle to the real deal. But, I found an easy trick to improve the texture of the dry mix falafel by adding baking soda. The improved texture does wonders for the taste as well. Here’s how you can make it.

Ingredients

Serves 4. 30 minutes.

  • 200 g dry falafel mix (for example from coop)
  • 2 teaspoons fresh lemon juice. You can also substitute it with vinegar or lime juice.
  • 0.5 teaspoon of baking soda or sodium bicarbonate (coop)
  • 1.75 dl boiling water

Recipe

This recipe makes the dry-mix falafel airier on the inside and gives it a nice crust.

  1. Start by following the package instructions. In my case, I add to a bowl and mix
    • 1.75 dl boiling water
    • 200 g dry falafel mix
  2. Let it rest for 15 minutes.
  3. Add to the bowl and mix

    • 2 teaspoons fresh lemon juice
    • 0.5 teaspoon of baking soda

    As soon as you add both, they start to bubble and form CO₂ and water.

  4. Heat a large skillet, once it’s hot add about 0.5 cm of olive oil or sunflower oil.
  5. With a spoon add some falafel mixture to the skillet. Usually, my mixture has a consistency that it forms a ~ 2 cm thick disc.
  6. After a minute or so, you start to see bubbles which drift to the surface. That’s the CO₂ making your falafel nice and airy.
  7. Turn the falafel after a few minutes when it’s browned on one side. Turning can be tricky since the top is still liquid.
  8. When the second side is done, put the falafel patties on a double layer of kitchen paper to drain some of the excess fat.

Enjoy in some pita bread or dürüm with some fried aubergine, pickled onions, tzatziki, hummus, pickled peppers or whatever strikes your mood.

Why I think it works

The baking soda makes the mixture slightly more alkaline, speeding up the browning reaction, which creates the crunchy exterior. I added the lemon juice to prevent the mixture from becoming too alkaline which can result in a soapy taste. In the end, the airy center due to the CO₂ was a positive by-product that I did not expect.

I also tested a batch with only baking soda. This batch produced a browner exterior due to the alkalinity of the mixture. However, it also produced some CO₂, although not as much as the lemon + baking soda combination. Perhaps the falafel mixture is already slightly acidic by itself?

The lemon + baking soda combination puffs up the falafel, whereas baking soda alone doesn’t seem to have much effect during baking.

But in the final result the baking soda alone has some air pockets.

Since the CO₂ made the falafel nice and airy, I also tried making a batch with baking powder. Using baking powder would make the recipe slightly easier because you only have to add one extra ingredient. Baking powder is a mixture of baking soda combined with some form of acid. Once baking powder dissolves in water, it starts to produce carbon dioxide. And once it’s heated, there is a second reaction that also produces CO₂.

I did one experiment where I added 2/3 of a teaspoon to the mixture at step 3. This falafel turned out the same as the one where I didn’t add anything. It didn’t become airy and also it browned the same as the standard recipe without additions. The fact that it didn’t brown like the baking soda batches is expected, because I assume that the base and the acid amounts are matched to not alter the pH of the mixture. However, I did expect to see some airiness, but that didn’t happen, or at least not noticeably.

Methods and results

Below is an overview of the methods I tried and their results. The method refers to whether something is added in step 3 of the recipe.

Method Result Grade
Baking soda + lemon juice Crunchy brown exterior, airy interior. 8
Only followed package instructions Soft crust, dense sticky interior 6.5
Baking powder Crunchy brown exterior, airy interior. 6.5
Baking soda Soft crust, dense sticky interior 7.5

Other than that, all preparation was kept the same as far as I remember. Most batches were made on separate days, with weeks between attempts. The only simultaneous batches were made where I added baking soda + lemon juice to one batch and only baking soda to a second one.

The amount of baking soda (or powder) and lemon juice did not vary. It was an initial guess/try. It could be that there is a better outcome with other ratios/amounts.

]]>
How to connect to a Siemens PLC from TIA portal on VMWare2023-02-15T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/simatic/2023/02/15/tia-vmConnecting to a PLC from a virtual machine can be challenging. In this article I show a step-by-step guide how to connect to a Siemens Simatic PLC. Although this process is probably be similar for other PLCs. I also show a few tips and tricks on how to debug the process.

  1. Check if you can see the PLC if you connect the PLC to your computer. If you get a ping reply, that is a yes.
    • ping 192.168.0.1
  2. If you get a reply go to the next step. If not, fix the IP of the Ethernet port in the same range as the IP address of the PLC.
    1. Go to Control Panel > Network and Internet > Network Connections
    2. Right click the network adapter where the PLC is connected to and select Properties.
    3. Select Internet protocol Version 4 (TCP/IPv4)
    4. For IP address enter the same numbers as your PLC for the first three parts. For the last one give it a unique number. In this case not .1, to prevent IP conflicts on your network. For example I used 192.168.0.10.
  3. Try to ping the PLC again. If that works, start the VM and try to ping the PLC from inside the VM.
    • ping 192.168.0.1
  4. If that also works, you can check to which port the PLC is connected with:

    > Test-NetConnection 192.168.0.1
    
    ComputerName           : 192.168.0.1
    RemoteAddress          : 192.168.0.1
    InterfaceAlias         : Ethernet0
    SourceAddress          : 192.168.11.128
    PingSucceeded          : True
    PingReplyDetails (RTT) : 1 ms
    
  5. Check which network connection the port is using, by selecting Properties of Ethernet0 in Control Panel > Network and Internet > Network Connections.
  6. Open your TIA project. Select your PLC and go to Online > Extended go online.
  7. Under PG/PC interface select he network connection you found at step 5 and click start search.
  8. Select GoOnline.
]]>
Playing with Chat-GPT and structured text2023-01-19T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2023/01/19/chat-gptChat-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.

Prompt: write a function in structured text which replaces all vowels with the numbers 1-5.

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

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.

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

Prompt: ReplaceVowels(“Apple”)

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”

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.

Prompt: modify the function such that you use a switch case instead of IF ELSE statements

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

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.

Very nice. Next improvement.

Prompt: use a new string to save the modified string into

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

Cool.

Prompt: 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.

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

Impressive! now lets try to get and END_VAR in there.

Prompt: you forgot the END_VAR

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

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

Prompt: 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,

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

It is becoming a hot mess. It forgot one END_VAR and moved the FUNCTION too far down. One more attempt:

Prompt: 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

Too many requests in 1 hour. Try again later.

¯\_(ツ)_/¯

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?

]]>
How to use MC_TorqueControl?2022-12-10T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2022/12/10/torque_controlMC_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.

There is also a YouTube video by Electrical Automation Hands-On who explains these steps.

What is MC_TorqueControl?

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 and force each time, I refer to both modes when I mention torque control.

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.

Below is the diagram from the Tune drive tab of Drive Manager 2. When you use torque control, Tset 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 Tset 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 Vset.

How to use torque control?

Make sure you have:

  • Drive Manager 2 installed
  • TwinCAT 3.1.4024.15 for both XAE (engineering) and XAR (runtime) or later
  • For AX5000: firmware 2.14 or later
  • For AX8000: firmware 1.03 Build 002 or later.

Then, follow these steps to use torque control:

  1. In the Advanced > Slot settings tab, select VelocityControl2 for the VelocityControlSlot of the respective axis. This enables the velocity limit.1
  2. In the Parameter list, select TorqueMightBeReducedToZero (1) at index 0x60CA. This is another setting to enable the velocity limit.
  3. In the Process data tab, expand the Ch A operation and select Cyclic_synchronous_torque_mode_CST (10) 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.
  4. In the MOTION > NC- TASK1 SAF > Axis > {your axis} > Encoder and Drive 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.

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

  • someAxis: is the axis you want to control.
  • forceOptions: defines a starting value of the torque when the force function block is activated. This ensures a smooth transition from the previous mode.
  • targetForce: the torque you want to apply.
  • TORQUE_RAMP: defines a maximum ramp for the torque.
  • VELOCITY_LIMIT: Limit the velocity.
  • ContinuousUpdate: If TRUE, then changes to targetForce are immediately applied by torque control, rather than needing to toggle force.Execute.2 Then you activate force control by setting forceMode to TRUE.
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,
);

After calling another motion function block, the operation mode is automatically switched back from torque mode into position mode. For example MC_MoveVelocity, MC_MoveAbsolute, MC_Halt etc.

Some final notes:

  • Lag monitoring switches off automatically if you use MC_TorqueControl with the AX5000.
  • MC_TorqueControl works with all SoE or CoE drives, but maybe the velocity limit function doesn’t work.
  1. 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. 

  2. For continuous force mode to work, the TwinCAT runtime needs to be at version >=4024.35 

]]>
TwinCAT datatypes in .NET2022-10-03T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2022/10/03/data-typesWhen 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.

IEC61131-3 System Manager .NET type C# Visual Basic Lower bound Upper bound Memory space
BOOL BIT System.Boolean bool Boolean 0 (FALSE) 1 (TRUE) 8 bit
BOOL BIT8 System.Boolean bool Boolean 0 (FALSE) 1 (TRUE) 8 bit
BYTE BITARR8 System.Byte byte Byte 0 255 8 bit
WORD BITARR16 System.UInt16 ushort - 0 65535 16 bit
DWORD BITARR32 System.UInt32 uint - 0 4.29·109 32 bit
SINT INT8 System.SByte sbyte - -128 127 8 bit
INT INT16 System.Int16 short Short -32768 32767 16 bit
DINT INT32 System.Int32 int Integer -2.15·109 2.15·109 32 bit
LINT INT64 System.Int64 long Long -263 -263-1 64 bit
USINT UINT8 System.Byte byte Byte 0 255 8 bit
UINT UINT16 System.UInt16 ushort - 0 65535 16 bit
UDINT UINT32 System.UInt32 uint - 0 4.29·109 32 bit
ULINT UINT64 System.UInt64 ulong - 0 264-1 64 bit
REAL FLOAT System.Single float Single -3.40·1038 3.40·1038 32 bit
LREAL DOUBLE System.Double double Double -1.798·10308 1.798·10308 64 bit

Sources

]]>
Why am I getting a page fault in TwinCAT?2022-09-18T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2022/09/18/page-faultEarlier 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.

Page faults in TwinCAT

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

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

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

What are page faults?

Wikipedia lists different types of page faults. 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.

Pointers

Pointers store the address of a variable. When you instantiate a pointer to a variable, the default address is 0. Therefore, in the example below pointerToNumber is 0. Then in the implementation part, I try to save the value to which the pointer points into number by dereferencing the pointer using the ^ symbol.

PROGRAM PointerExample
VAR
    pointerToNumber : POINTER TO INT; // 0
    number : INT;
END_VAR

number := pointerToNumber^;

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.

Solution

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:

PROGRAM PointerExample
VAR
    pointerToNumber : POINTER TO INT;
    number : INT;
END_VAR

IF pointerToNumber <> 0 THEN
    number := pointerToNumber^;
END_IF

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 CheckPointer.

The CheckPointer function is automatically called each time 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.

To add the CheckPointer to your project, right click your PLC project and select Add > POU for implicit checks.

Select Pointer Check and confirm with Open.

This adds the CheckPointer 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.

Another solution would be to pass the pointer via VAR_IN_OUT or use constructor injection via FB_init as I showed in the earlier article.

References

Another way you can get page faults is through references, as I also showed in an earlier article. References are pointers with an improved interface. Thus it shouldn’t come as a surprise that these can cause page faults as well.

The short example below causes a page fault: I instantiate a reference to an integer called number. Then I try to assign a number to this reference.

PROGRAM ReferenceExample
VAR
    refNumber : REFERENCE TO INT;
END_VAR

refNumber := 1;

When you try to assign a number to this reference, you get a page fault, because refNumber doesn’t refer to anything.

Solution

In the earlier article I showed several ways you can prevent page faults from references. They were using:

  • __ISVALIDREF
  • Adding references to VAR_IN_OUT
  • Or using constructor injection via FB_init

Here I only show the first solution with __ISVALIDREF. You use this function in a similar was as you would with the pointer check (somePointer <> 0): The full example becomes.

PROGRAM ReferenceExample
VAR
    refNumber : REFERENCE TO INT;
END_VAR

IF __ISVALIDREF(refNumber) THEN
    refNumber := 1;
END_IF

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 ELSE clause with an appropriate error message. Even better would be to pass the reference via VAR_IN_OUT or FB_init if possible.

Interfaces

Interfaces can also cause page faults as shown by this example. I defined an interface I_Interface with a single property called SomeProperty which returns an integer.

INTERFACE I_Interface

PROPERTY SomeProperty : INT

I create an instance someInterface of I_Interface. Then I try to save the integer returned by this into the variable number.

PROGRAM InterfaceExample
VAR
    number : INT;
    someInterface : I_Interface;
END_VAR

number := someInterface.SomeProperty;

Executing the code again results in a page fault. That is because a function block needs to be assigned to the interface first. Then someInterface 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.

Solutions

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.

PROGRAM InterfaceExample
VAR
    number : INT;
    someInterface : I_Interface;
END_VAR

IF someInterface <> 0 THEN
    number := someInterface.SomeProperty;
END_IF

Again this solution silently fail, thus it might be wise to add an ELSE clause with an error message. Or if you use the interface in a function or function block you can use the VAR_IN_OUT or FB_init solutions mentioned in the earlier article.

Conclusions

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 __iSVALIDREF for references.

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.

]]>
TwinCAT/BSD installation and tutorial2022-08-11T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2022/08/11/twincat-bsdTwinCAT/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.

What is TwinCAT/BSD?

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.

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.

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.

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 the official manual or in this cheat sheet.

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.

If you would like to get more details on Tc/BSD, watch the official Beckhoff video or read the manual for in-depth information.

Virtual machines ❤️ Hyper-V

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 Docker which requires Hyper-V. Unfortunately, once you enable this and you try to run your TwinCAT code locally, you get an error:

While researching how to circumvent this restriction, I came across a Reddit post 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 StackOverflow question 🥳. Furthermore, I found out that it also works with Virtual Box.

Installing Tc/BSD on VirtualBox

Installing Tc/BSD on Virtual Box is quite straightforward, thanks to an install script from the community. Furthermore, there is also a video by Coding Bytes describing installation.

To install Tc/BSD:

  1. Download and install Virtual Box.
  2. Download or clone the install script.
  3. Go to the Beckhoff website and download the TwinCAT/BSD ISO.

  4. Extract the contents of the installer script and the Tc/BSD ISO. Copy the contents of both to a single folder.
  5. Copy the exact filename of the Tc/BSD ISO image and open Create-TcBsdVM.ps1 or TwinCAT BSD VM creator.bat with a text editor, depending on which you want to use.
  6. Change the ISO filename in one of the installer scripts to the one which matches your downloaded ISO file. Here "TCBSD-x64-13-55702.iso" is the name of the ISO file I downloaded.
    • Create-TcBsdVM.ps1 at line 7: $tcbsdimagefile="TCBSD-x64-13-55702.iso",
    • TwinCAT BSD VM creator.bat at line 2: SET sourcefilename="TCBSD-x64-13-55702.iso"
  7. Save and close the installer script.
  8. Then either double click TwinCAT BSD VM creator.bat to execute, or from powershell run Create-TcBsdVM.ps1 TcBSD-VM. Here the first argument, TcBSD-VM, is the name of the virtual machine.
  9. 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 TC/BSD Install and hit enter.

  10. Then select the 4G hard drive which was automatically created by the script and hit enter.
  11. Hit enter again to acknowledge the warning.

  12. Then it asks for you to generate a password. Type in a password and repeat it to make sure it’s correct.
  13. After several minutes the installation is complete and you see the following screen. Select OK.

  14. In the new menu select Shutdown.

  15. Open the Settings > Network of the virtual machine you created. Now there are different options for different use cases:

    • If you just use the runtime locally, enable a Network adapter 1 and set it as a Host only adapter

    • If you want to set up a physical connection to an outside network select Bridged adapter

    • If you want the Tc/BSD package manager to work, you need to enable both. For adapter 1, select Host only adapter and enable a second adapter where you select Bridged adapter.

  16. Verify that the network connections work by starting the virtual machine, logging in and:

    • Run ifconfig . You should see an inet starting with 192.168..

    • You can also try to ping www.beckhoff.com and see if you get a reply. For me, it took a minute before the network connection started to work.

	$ 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
  • In case it is not working, check your network adapter settings. Type Ethernet settings into the start menu and open it. Select Change adapter options and right-click VirtualBox Host-Only Network and select Properties. Make sure they are set to automatic.

Installing Tc/BSD on VMware

Installing TwinCAT/BSD on VMware is a bit more complicated. The steps below are based on a YouTube tutorial from Jakob Sagatowski. Before you head over there, I did notice that I had to do several things differently for it to work.

Create a bootable USB

  1. Install Rufus to create a bootable USB drive.

    • Install via the terminal winget install -e --id Rufus.Rufus.
    • Or download manually and install. You can also use the portable version, which doesn’t require any installation.
  2. Go to the Beckhoff website and download the TwinCAT/BSD ISO.

  3. Unzip the file you just downloaded. You’ll probably find three files in there. The ISO one is the one you need.

  4. 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 😱.

  5. Open Rufus, click SELECT and select the ISO file you just unpacked.

  6. Then select START. It then formats the USB drive and makes it a bootable USB drive with Tc/BSD on it.

Installing TwinCAT/BSD

  1. Download and install VMware Workstation Player. It’s free for non-commercial use.

    • Install from the terminal with winget install -e --id VMware.WorkstationPlayer
    • Or manually download and install.
  2. Select the free license or enter a license number

  3. Start VMware Workstation Player with Administrator rights and select create a New Virtual Machine. You need administrator rights later to access the USB drive.

  4. Select I will install the operating system later.

  5. Select “Other” and “FreeBSD 12 64-bit”

  6. Give your virtual machine a descriptive name and change the save location if you would like to.

  7. Specify the disk capacity. I left it at the default 20 GB. Note: Jakob 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 error1 once I remove the USB drive.

  8. You see a screen summarizing the results. Click Finish.

  9. Select the virtual machine you just created and select Edit virtual Machine Settings.

  10. 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.

  11. Add the USB drive as a hard disk by clicking on Add.

  12. Then in the new window, select Hard Disk and click Next.
  13. Select SATA and click Next.
  14. Select Use a physical disk (for advanced users). Yes, that’s you :D. Click on Next.
  15. Now you get prompted to choose a disk. Here you want to select your USB drive, which should still be on your computer.

  16. To find out if you need to select Disk 0 or Disk 1. Type in diskmgmt in the start menu to open Create and format hard disk partitions.

  17. 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.

  18. Now go back to the virtual machine settings and select PhysicalDrive1, Next and Finish.

  19. Finally, you set the firmware type of the USB drive to UEFI. If you have a Pro version of VMware, you can set the firmware type in the virtual machine settings under Options > Advanced > Firmware type and select UEFI. If you have the Player version, you:

    1. Locate the directory of the virtual machine. You can find its location under Edit virtual machine settings > Hard Disk (SCSI)

    2. Open the VMX file in the virtual machine directory with a text editor.
    3. Find the line firmware = "bios" and replace with firmware = "efi" and save it. If this line doesn’t exist, add it somewhere.

  20. Now start the virtual machine with Play virtual machine.

  21. Wait for the installer to start up. This can take a minute. After that, you see the following screen. Select TC/BSD Install and hit enter.

  22. Then select the 20G virtual hard drive you made and hit enter.

  23. Hit enter again to acknowledge the warning.

  24. Then it asks for you to generate a password. Type in a password and repeat it to make sure it’s correct.
  25. After several minutes the installation is complete and you see the following screen. Select OK.

  26. In the new menu select Shutdown so that you can remove the USB drive.

  27. Remove USB drive under Edit virtual machine settings. Make sure you don’t select the 20 GB hard drive, on which you just installed Tc/BSD.
  28. Go back to the main menu and start the virtual machine.
  29. After some time you see the login screen. Log in with your user name and password you made during the installation.

  30. Congrats. You now have a running version of TwinCAT/BSD.

Device manager

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 ifconfig. The IP is right after inet.

Then open your browser and type in the IP address you found. For me, it’s https://192.168.126.128. It’s possible that a warning shows up. Accept the risk and continue.

From here you can log into the device manager with the username Administrator 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.

Web console

Next to a device manager, the https://192.168.126.128 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 scrolling2 and copy-pasting commands is easier.

Installing packages

As I mentioned earlier, there is no GUI. You do most actions from the terminal. One of the things you can do is install packages, with a package manager. Package managers are a useful feature in UNIX-like operating systems. Although Windows is now also joining the party with winget.

Installing TwinCAT HMI server

Note: See also a video by Coding Bytes which describes this step.

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 pkg search hmi, you see two results.

$ 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

The first one is the old HMI and the second one is the one I want. To install it, type doas pkg install TF2000-HMI-Server and hit enter. Confirm the start of the installation with y. Then a warning showed up. So I did doas service TcHmiSrv start to start it.

=====
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 !!!! ==========================

If you then type top and hit enter, you see TcHmiSrv is running. top 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 top, press q or Ctrl + C.

Running PLC code

Connecting to the virtual machine

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 Choose Target System > Search (Ethernet) > Broadcast search. 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 OK to start the search.

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

After connecting to the PLC, you should be able to activate your configuration. Finally. You can develop TwinCAT code again while sitting in a beer garden like some developers do. Freedom!

Publishing the HMI

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.

Once you have that, you need to add an exception to the Tc/BSD firewall. 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 doas ee /etc/pf.conf from the console. Then add the following new line at the end of the file

# allow TcHmiSrv outgoing
pass in quick proto tcp to port 1010 keep state

Exit the ee editor with Esc, select a) leave editor, save your changes and restart the virtual machine.

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 SYSTEM > License. If it’s not present, add it manually via the Manage Licenses 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 Validate connection. If the connection was successful, select Publish.

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.

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.


  1. The error message:

    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@:/
    

  2. 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 TcHmiSrv --help, 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, shows the previous command and Page Up just shows a ~. After consulting Google, I found out that is where the Scroll Lock key is for. I always wondered what this button did. Note: If you’re on a laptop without a Scroll Lock key, you can remap one of the existing key combo’s. scroll lock enables scrolling in the TcBSD terminal window 

]]>
No more formatting fights with pre-commits for TwinCAT2022-04-14T00:00:00+00:002025-08-01T14:25:53+00:00https://cookncode.com/twincat/2022/04/14/pre-commitEarlier 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.

Use the GitHub TwinCAT template repo to set up a TwinCAT repo, including the pre-commits.

What are pre-commits?

Pre-commits are part of a class of so-called git hooks. 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.

You can see some examples of git hooks if you have a project which uses git. Navigate to the .git/hooks folder and you should see a list of example hooks there. If you can’t see the .git folder, make sure you have enabled “Hidden items” under the View tab in your Windows Explorer.

git hook examples as found in the ./git/hooks folder

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.

You can write your own hooks from scratch, but for most files, there is already a large variety available. Let me show you how you can use these and a structured text hook for your TwinCAT projects.

Setup pre-commit

A popular framework to manage pre-commits is pre-commit. It’s a Python based framework and thus you need a working Python installation on your system before you can use it.

  1. (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.

    • Manually download and install Miniconda or Anaconda.
    • Or run winget install -e --id Anaconda.Miniconda3 or winget install -e --id Anaconda.Anaconda3 from the terminal.
  2. Install pre-commit with either:

    • pip install pre-commit
    • or conda install -c conda-forge pre-commit
  3. Check if the installation went OK by running the command below in the terminal. If you see a version number then pre-commit works.

    $ pre-commit --version
    pre-commit 2.17.0
  1. Next, create a new file called .pre-commit-config.yaml in the git project folder (same folder as where your .git folder resides) where you want to start using pre-commits. Now you’re all set up to start using pre-commits.

TwinCAT relevant pre-commits

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 .pre-commit-config.yaml file:

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

For completeness, I’ll also add the following standard pre-commits. You can first run these pre-commits. Later I go into details about what they do and why they are useful.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      # Removes trailing white spaces
      - id: trailing-whitespace
      # Checks yaml files for parseable syntax
      - id: check-yaml
      # Prevents git from committing large files
      - id: check-added-large-files

Install the pre-commit hooks with pre-commit install. This needs to be done once per git repository. Afterwards, you can run pre-commit run --all-files 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 git commit .... In this case, the automatic execution limits to the changed files.

Depending on the project you see no, some, or a lot of changed files. For example, when I ran it on my TwinCAT Tutorial repo I saw the following:

pre-commit changes to the twincat tutorial repo

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

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:

git diff after running the sample pre-commit. Here trailing white spaces have been removed and tabs are replaced by spaces.

You see that it removed a trailing space after the CASE _state OF. Additionally, the file had a mix of tabs and spaces. The tabs were replaced by spaces. These changes were made by the trailing-whitespace and twincat-leading-tabs-remover respectively. For a full list of all the changes, you can see the differences of this commit.

Hooks what and why

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

twincat-leading-tabs-remover

What: replaces all leading tabs with four spaces.

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

twincat-lineids-remover

What: removes LineIDs from a POU file.

Why: they are only useful locally. When uploaded to source control they only cause visual clutter. For more information see point 4

twincat-xml-format

What: formats the .tmc and .tcp files with newlines and indentation.

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.

check-fixed-library-versions

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

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

trailing-whitespace

What: removes spaces and tabs at the end of lines.

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.

check-yaml

What: checks that programs can read your YAML file. For example, the .pre-commit-config.yaml one.

Why: ensures that your YAML files do not break.

check-added-large-files:

What: prevents git from adding large files to its history.

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 Git LFS.

Developing your own hooks

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. The TwinCAT hooks you saw earlier, is an example of a remote hook.

You can use many languages 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.

Remote hooks also need to be a valid git repo. That is because pre-commit tries to do a git clone ... of the repo URL you supplied in the .pre-commit.yaml file. To make a remote hook, see the TwinCAT hooks repo as an example.

Local hook example

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 https. If they don’t, it replaces http with https.

First I added a file called .pre-commit-config.yaml with the following content. See the comments for their meaning.

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

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

Next, I created a new file called _hooks/check_https.py with the following content. See comments for the meaning.

#!/usr/bin/env python

import argparse

def fix_file(filename):
    # Open the file
    with open(filename, 'r') as fd:
        original_lines = fd.readlines()
    new_lines = []
    changed = False
    # For each line in the file check if a https:// is found.
    # If so, replace it with https://
    for line in original_lines:
        if "https://" in line:
            line = line.replace("https://", "https://")
            changed = True
        new_lines.append(line)

    # If a line was changed, print a message in the console and overwrite the
    # original file with the fixed one.
    if changed:
        print(f'Fixing {filename}')
        with open(filename, 'w') as fd:
            fd.write(''.join(new_lines))


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


if __name__ == "__main__":
    exit(main())

If you now run pre-commit run --all-files you should see all the files it changed.

> 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

Further ideas

Other ideas could be to use Prettier to format JavaScript, HTML, or CSS files from HMI projects. Unfortunately, TwinCAT saves the HMI pages as .content and .view files. These files are not recognized as HTML files by prettier, but you could probably make it work by temporarily renaming these files to .html and then running prettier.

Pre-commits are automatically executed locally whenever you commit something. But you can also add pre-commit to your CI workflow. For example, use prettier.ci to automatically format markdown files of a pull request.

pre-commit integration in github actions changes made by pre-commit.ci

Have you already used pre-commits for your (TwinCAT) projects? Or do you have other ideas? Let me know in the comments below.

]]>