Source control tips for TwinCAT

Source control is essential when you’re developing software. However, there is little information online on how to do it properly for TwinCAT projects. In this post I’ll share some tips and tricks I picked up along the way. The main focus will be git, but many points apply to other source control systems as well.

What is source control?

If you’re not yet familiar with source control then it is something which you should absolutely learn as a software developer. Source control (or version control) allows you to incrementally save your code in a kind of database. All your changes are saved and it is very easy to go back to a previous state of your code. This reverting can be very convenient if you discover that a single feature you implemented, is not quite panning out as you planned. In this case it is usually very easy to either reset your code to the state before the change, or even just undo the single feature. Finally it can also be used to prevent versioning in filenames.

PhD comics "final.doc"

Git

The most popular versioning control system is called git. Git is a free and open source distributed version control system. Distributed means that every developer has a copy of the full history of the code on their own machine. Each developer can make changes locally first and save each incremental step. Once they are done, they can merge their changes into the main code base. This process contrasts to centralized source control, where you will always need an active connection to add your changes to the history.

It is however not necessary to have your code history on some central server or on several developer’s computers. You can also use git locally to version control any document on your computer.

Although git, and the ideas behind it are very powerful, it is also notoriously difficult to master. So much so, that there are multiple billion dollar companies like GitLab and GitHub, which provide a more user friendly interface to git. Relevant XKCD:

XKCD on gits complexity

There are also other tools which make working with git easier on your local system. If you’re just getting started with git, I recommend installing some git GUI like SourceTree, GitKraken or ungit. You’ll learn to use the git command line with time. But even after gaining experience, I still like to use one of the GUI’s for most git commands.

0. Choosing the right implementation language

The first step for easy TwinCAT source control already starts with the selection of the implementation language. There are various implementation languages for PLC code such as: ladder logic, function block diagrams and structed text. Some of these implementation languages work better with source control systems than others. I mainly have experience with function block diagrams and structed text and I’ve noticed a big difference between how well they work under source control.

Let me start with a disclaimer. I’m a bit biased on the structed text front. I think structed text is the future of plc programming, because it is much more versatile than the others. For example making a for loop, or an if else statement, in function block diagrams is impossible or a lot more verbose than in structed text.

With that out of the way, I’ll now go into why structed text works much better than function block diagrams for source control. When you’re using anything other than structed text, the raw text files are basically unreadable. Furthermore minor changes in function block diagrams, like swapping the order of two function blocks, can lead to a code difference of dozens or hundreds of lines.

That is why you need a special tool, in TwinCAT’s case the TcCompare tool, to merge changes or view differences between versions. This severely limits your options. For example if you just have access to the git command line, the GitLab web interface or your source control GUI, then making sense of the changes between versions is practically impossible. Only structed text works with any system.

1. .gitignore

The .gitignore file is very important file when you’re using git. Git uses this file to determine of which files or folders the changes can be ignored. Files and folders which can be ignored usually fall into two categories. In the first category are files which can be created from the other files. For example the .tmc file is compiled from your plc code. Or if you have some documentation in a mark down file from which you generate a html page. In these cases you only want to have the plc code or the mark down file under source control. A second category are user specific files. For example the .sou file from Visual Studio, which is a user’s options file for the Visual Studio project.

Beckhoff already provides a list which explains where most file extensions are used for. They also list whether files should be under source control or not. Jakob Sagatowski added some more files to this list and made a general TwinCAT3 .gitignore file. This file gives you a good starting point with any git source controlled TwinCAT project.

2. Creating independent files

Once you determined which files should be source controlled, it is important to make sure those files only change when needed. Basically you want to make sure TwinCAT saves closely related settings and code in separate files. Several separate files which can be created are:

  1. NC axes or IO devices can be saved in a separate file as explained on this page on InfoSys.

  2. On the same page it mentions that you can save the plc project as a separate file. Alternatively a stand-alone plc project can be created as I wrote about earlier.

  3. Events from the TwinCAT EventLogger are normally stored in the .tsproj file, but can also be stored in an independent file. To do this right click on Type System and select Add New Item…

    image-20210605125836430

    Choose a name and location to save your new event class. When you select the tmc file, the new event classes stored in this tmc file are shown. You can then add and edit any events and they will be stored in the separate file.

    image-20210605130305083

    When you select type system the newly added event class will also show up among all other available event classes.

    image-20210605130336594

    When you save the events in a separate tmc file, make sure you add the event tmc to source control! Since the GitHub TwinCAT3 .gitignore excludes all tmc files.

  4. Per default so called LineIDs are saved in the .TcPOU files. The LineIDs are used for

    breakpoint handling, for example, and ensure that the code lines can be assigned to machine code instructions

    There are no adverse effect from saving them in a separate file, so I’m not sure why this is not the default. Especially since these LineIDs often change, even if no code changes were made. Once this option is enabled (under Tools > Options >TwinCAT > PLC Environment > Write options) the LineIDs are saved in the LineIDs.dbg file. So if you enable this option, make sure this file is then added to the .gitignore file, or else all will be for nothing.

3. Formatting rules

With the above measures implemented source control of your TwinCAT3 project has already improved significantly. There are some additional measures we can take. One of them is making sure you format your code in such a way that is makes comparing code differences easier when viewed in plain text.

One example which can make a difference is a new white line at the end of a declaration or implementation part. Take the following very short example:

Counter := Counter + 1;
a := Counter * 2;

When we look into the .TcPOU file itself this part looks as follows:

<Implementation>
	<ST><![CDATA[Counter := Counter + 1;
	a := Counter * 2;]]></ST>
</Implementation>

If we now make a very minor change to the code. For example add b := a/4; on the last line. The plain text difference of this change then looks as follows:

<Implementation>
	<ST><![CDATA[Counter := Counter + 1;
-	a := Counter * 2;]]></ST>
+	a := Counter * 2;
+	b := a/4;]]></ST>
</Implementation>

If instead, this change was done on a file with a new line at the end, then the diff would look like

<Implementation>
	<ST><![CDATA[Counter := Counter + 1;
    a := Counter * 2;
+   b := a/4;
	]]></ST>
</Implementation>

By adding just a single white line we went from a difference of -1/+2 to -0/+1! There are a few more of these tricks. Have a look at the style guide of the structured text formatter (TcBlack) I’m building for more ideas. Unfortunately TcBlack is quite limited in functionality currently. There is however a fully functional paid alternative called STweep. You could use this, with the options outlined in the TcBlack style guide, to achieve minimal differences.

4. git filters

Git filters can be used to create a sort of .gitignore for specific lines of code. Whenever you checkout or commit a piece of code it can go through the git filter. If the file matches a filter setting, you can run a script to change some code before it is actually committed or checked out.

Let me show you how it could be used. Suppose you are working on a project with a few people. Everyone has their own development PLC with a different target net ID. When you select your development PLC’s net ID, this change will be saved in the .tsproj file:

<Project 
	ProjectGUID="{DEF6D51E-ADA4-4CFE-B6F1-CB3CC59478BE}" 		
	TargetNetId="1.23.456.78.1.1" 
	ShowHideConfigurations="#x106"
>

Since your colleagues NetId’s will be different, you want to prevent this change from being included into the code’s version history. As also explained in this StackOverflow post you can achieve this with the following steps:

  1. In the .git/config file inside the project you add the following:

     [filter "ignoreNetId"]
         clean = sh ".git/ignoreTargetNetId.sh"
    

    Here you define a filter called ignoreNetId which defines a clean step. The clean gets triggered when you add a file to a commit (git add). When the clean gets triggered it will call a bash script ignoreTargetNetId.sh, which we will define below.

  2. Add the following line to the .gitattributes file:

     *.tsproj filter=ignoreNetId
    

    This command means that if a file with extension .tsproj is committed or checked-out, git will call the ignoreNetId filter we defined previously.

  3. Finally you make a new file called ignoreNetId.sh in the .git folder and add the following content to this file:

     sed --regexp-extended "s/}\" TargetNetId=\"[0-9.]+\"/}\" /g" "$@"
    

    The sed command is a text replacement command line tool. The --regexp-extended option means we can use most regular expressions to find the TargetNetId. The regular expression which does the search and replace is "s/}\" TargetNetId=\"[0-9.]+\"/}\" /g". The search part ("s/}\" TargetNetId=\"[0-9.]+\"/) searches for a target net id with an arbitrary net id. Once this pattern is matched it will replace is with }\" , thus effectively removing the whole net id part. With the net id removed, the project then selects the <Local> TwinCAT run time when opened. The last part "$@" is where the file name argument will be filled in, once this script is called from the first step.

Summarizing; the above steps ensure that whenever you select your development plc, this change will never show up as a change in git. You can also do the the reverse: that on a checkout it automatically adds your developments plc net id into the target net id. Then you do not have to select it each time you check out the code. See this StackoverFlow post for more info.

I’ve also used the filters to remove random changes to the HMI port in the .hmiproj file and an automatic add/removal of development specific compiler definitions.

I hope I shared some useful tips. What kind of tricks do you use?​

Discuss: r/TwinCAT, r/plc.

Found a mistake or a typo? Suggest a change!