Hello everybody,
What is the best practice for reusing the same parts of a code in KUKA robots? Let's call them "libraries".
Like many of you, I am trying to reuse the same parts of a code in all my projects, but the problem is that from time to time I update my "libraries" and then I have more than one version of the same library (in other programming languages it is called dll hell).
In my experience, some issues in a project are very easily solved just by updating a "library", because the same error occurs in all the projects where I used the same code.
I can try to document what "libraries" are used in projects, but I do not think that it is very efficient.
I am also programming in C#, so I am spoiled with features like nugets
For the version control I am using git. Git has a feature called submodules and for "libraries" it worked just fine until Work Visual deleted all my project files in the repository......
As I understand, Work Visual does not like unfamiliar file extensions.
Using a submodule in a git project enables me to see if a "library" has any updates. With a few button clicks I can have all of them in my main project.
Any advice on this topic?
What is the best practice for reusing the same parts of a code in KUKA robots?
-
Aivaras Narbutas -
June 26, 2023 at 5:31 PM -
Thread is Unresolved
-
-
Lemster68
June 26, 2023 at 5:44 PM Approved the thread. -
Same as for any programming language, really.
- Make code as modular as possible
- Keep routines/functions as small as possible, that do one thing and do them well
- Make code as self-contained as possible
- If a "library" routine has dependencies on other library routines, try to combine them into a single module
- Avoid writing libraries that require making changes to non-library files, like $CONFIG.DAT.
- If you are creating special STRUCs or ENUMs, that non-library routines might need to pass or accept, they should be DECL'd GLOBAL in the .DAT file of your library module.
- Avoid GLOBAL variables except when necessary (see 2.3). Write your routines to accept passed arguments and pass back results, rather than relying on Module-level or GLOBAL variables, to the greatest extent possible.
- Make code configurable. For example, Interrupts are System-level resources by definition. If your library uses an Interrupt, chances are you'll eventually run into a robot where that Interrupt number is already in use by someone else's program. So instead of using "INTERRUPT 17" in your library routines, use "INTERRUPT nInterruptBlah", where nInterruptBlah is an INT DECL'd in the library's .DAT file. That way, when the conflict over Interrupts happens, you can simply change one variable in your Library to fix it.
- Avoid hard-coding values as much as possible, unless you know there's an unchangeable limit. For example, $OV_PRO can never go outside 0-100, so limit-checking a value to be between those limits before writing it to $OV_PRO could be hard-coded without any issues.
- Any library routine should do robust checking of arguments, and post some kind of error message in the event that it's passed a bad argument. In dangerous cases, you may want the library routine to stop everything. In others, you may want that routine to pass back an error flag/variable/value, and have the higher-level routine decide what to do about it.
- Don't leave any holes where a strange input argument value could cause the library routine to do something bizarre. Keeping all routines as small and single-purpose as possible helps ensure this, making testing easier.
- A library module can contain both local and Global routines/functions. Only Globalize the ones that need to be called from "outside" the library. All the other routines that are only use "internally" should be kept local.
- Arguments are "left-justified." That is, if a library routine is DEF MyRoutine(Arg1,Arg2,Arg3), it can be called by just MyRoutine(Arg1). So if you ever need to "expand" the argument list on a routine, add the new arguments at the right-end end: DEF MyRoutine(Arg1, Arg2,Arg3,Arg4). This way, older routines that only pass 1-3 arguments can still call that library routine. This lets you improve library routines without breaking backwards compatibility.
- Of course, the improved library routine needs to check which arguments are present (or not), and react accordingly.
- Make code as modular as possible
-
To expand on points 2.3 and 3 in my previous post:
If you get into a situation where you need to pass many arguments to/from a routine, the temptation to use Global variables grows. This is one of the main reasons I would start creating custom STRUCs -- I could "package" a large number of arguments into a single custom STRUC variable and pass it as a single argument. This isn't always the best solution, but I often found it handy.
Also, regarding point 7: STRUCs and ENUMs can help with backwards compatibility. You can easily add new "members" to a STRUC or ENUM without breaking any old routines that use the "original" version. I leveraged this to help my backwards compatibility -- if I improved my library, adding more members to the STRUC or ENUM variables, I could backport the new library version into older KRCs without fear of breaking anything -- the older KRCs just wouldn't use the new "members".
Removingmembers from an existing ENUM or STRUC is very breaking, though. So that should be avoided. Of course, this creates a potential issue: if you keep adding to an ENUM or STRUC over many years, you might eventually hit a limit of what KSS can handle (255 characters per line, IIRC, is one limit). So you have to keep an eye out for that.
-
Thanks SkyeFire, awesome post!
-
Hello All
Great post from SkyeFire (as usual)
Can I just add... Put a lot of of comment lines and versionning as a comment.
When you will dive back into code years from now you will find this useful !
About point 2.3, what's the best way:
Put the declaration in the .dat files link to the .src
Create a new .dat files with all the declaration of the new library ?
BR
-
Can I just add... Put a lot of of comment lines and versionning as a comment.
When you will dive back into code years from now you will find this useful !
Derp. Yes, I tend to take some ribbing about how I over-comment my code.
Most of my modules have a version number, date-of-change, and who-changed-it in the comments at the top of the file.
About point 2.3, what's the best way:
Put the declaration in the .dat files link to the .src
Create a new .dat files with all the declaration of the new library ?
So, by KRL definition, any variable DECL'd in a .DAT file is automatically available to any/all routines in the .SRC file with the same name. So it has a scope of "module."
As such, dependencies should be modularized the same way routines are -- that is, if you "bundle" a set of related routines into a single module's .SRC file, then any "module scope" variables should be DECL'd in that module's .DAT file.
So, I would keep my .DAT files and my .SRC files mostly on a one-to-one basis. Exceptions would be a module that doesn't need any .DAT file. I suppose if you wanted to, you could create one giant GLOBAL .DAT file that would underpin all your library .SRC files, but that feels kind of kludgy. It also breaks the "Globals only when absolutely necessary" rule -- if your library Global variable has the same name as someone else's Global variable, one of you is going to have to change. So minimizing the "conflict surface" is a wise precaution.
Also, KRL doesn't handle name collisions between Global and Local variables very well. Or, perhaps it handles them too well. If this happens, the compiler will simply default to using the local variable, and you won't even get a warning. Makes debugging really hard. WorkVisual will give you a warning, but the robot will not.
As a general rule, I rank variables in order of precedence:
- If you can do it with a "routine"-scope variable (DECL'd between the DEF and END of a routine), and/or passed arguments, do that instead of using a Module variable in the .DAT file
- If you have to break rule 1, but can get away with using a Module variable, do that
- Only go Global if you absolutely cannot get the job done with Rule 1 or 2
- Never put your variables into $CONFIG.DAT unless you have no alternative