TagKreator: A goofy name for a maybe-useful project

GitHub - paul-griffith/tagKreator

So, I’m “releasing” v0.1 of a side project I’ve been working on for a little while.
It was mostly an exercise for me to learn more about Kotlin (an alternate JVM programming language, like Java or Jython) - but the end result might just be useful to some of you out there, so I figured I’ll post it up.

Basically, it’s a means to programatically generate (in a relatively type-safe way) JSON files that match the format of Ignition’s tag exports. What the heck does that mean? Well, basically, code like this (in Kotlin):

memory<Int>("Integer") {
    value {
        123
    }
    documentation = "My TagKreator tag"
    history {
        provider = "MySQL"
        enabled = true
        sampleRate = Duration.hours(1)
    }
    scripts {
        valueChanged {
            """system.util.getLogger("log").info(str(currentValue.value))"""
        }
    }
    security {
        read(AllOf) {
            levels {
                level("Authenticated") {
                    level("Administrator")
                }
            }
        }
    }
}

Is output as the following JSON:

{
    "tagType": "AtomicTag",
    "name": "Integer",
    "value": 123,
    "documentation": "My TagKreator tag",
    "readOnly": false,
    "readPermissions": {
        "securityLevels": [
            {
                "name": "Authenticated",
                "children": [
                    {
                        "name": "Administrator",
                        "children": []
                    }
                ]
            }
        ],
        "type": "AllOf"
    },
    "eventScripts": [
        {
            "eventid": "valueChanged",
            "script": "system.util.getLogger(\"log\").info(str(currentValue.value))"
        }
    ],
    "historyEnabled": true,
    "historyProvider": "MySQL",
    "historySampleRate": 3600,
    "historySampleRateUnits": "SEC"
}

But more than that, you can also do whatever string interpolation, loops, etc you could possibly want - Kotlin is a full-featured programming language; one way to think of this is as a multi-instance wizard on steroids.
Another fun possibility - since Kotlin’s serialization to JSON is bidirectional, you could import a given tag export, run some code to change it, then dump the result back out to JSON.

Because it’s in such a raw state, this is very much something that should be approached with caution - this is a personal project I’m giving to the community in case it’s useful - don’t expect any guarantees of support, future features, etc. I’ve only done basic testing, but have successfully created a variety of tag exports - however, I’m at a point where I want to see if it’s actually useful to anyone before I invest too much more time (I’ve completed my primary objective of learning a bunch about Kotlin DSLs).

Please do let me know if you have questions or feedback on anything, though. If you’re interested, I’d recommend checking out the readme on Github - that’s probably going to stay up to date better than this post.

7 Likes

Hi Paul, looks interesting, but forgive my ignorance, could this not be achieved in the same way with Python? For Ignition users in particular, Python is a given that we either know the language (at least as well as we need to) or we will know it in the future as it supports the active Ignition project dev that we’re working on. Personally, I couldn’t see myself learning another language unless it’s directly tied to dev work i’m doing. Python has the json library that can be used to convert between json and Python lists/dictionaries which are easy to work with. There is some initial effort involved in getting your head around the tag json structure and how to work with it, but once you do that and you have a foundation of code in place to work with it, it’s fairly easy to expand upon that to create new functions to do other things with it.

I’ve created many tag tools for our own development in Python 3.9 which work on the contents of the clipboard. I could have also used Ignition’s Jython, but I find running the Python tkinter GUI, albeit clunky, outside of Ignition far easier and more portable - I’ve used pyinstaller to convert all of my scripts into a single executable which runs the (ugly) GUI, although the environment its run in still requires that Python3.9 and the pywin32 library are installed.

Some of the scripts I have are (sorry I don’t mean to hijack… just some food for thought for how other integrators use Ignition and what tools they find themselves needing):

  • tag gens that take excel tables copied to the clipboard and convert them to json.
  • take a table from excel with tag paths and a list of other columns that define tag property names and have Ignition read the tag property values and spit them back into the same table format (this one is Jython run in Ignition since I use tag.read)
  • pull tags out from tag json copied to the clipboard and their config into an excel format (complex value properties just come in as json for example alarm definitions, but that’s fine for me)
  • a script to plot out the UDT nesting structure (e.g. find the usage of UDTs within definitions themselves)
  • similarly to above, generate a UDT usage table to find what UDTs aren’t being used, taking into account any that are nested/are parents of UDTs
  • convert between OPC and Memory valueSources
  • various other things like auto-gening the property bindings of alarms within UDTs, fix alarm property bindings when migrating from v7 to v8, remove all property overrides from UDT instances
  • tag compare tool to compare the differences between two tag json files (a generic comparator like in Git is useless for comparing tag json as it’s unaware that the true keys are the paths to the tags)
  • converting a dataset whose contents was copied to clipboard and pasted into Excel, back from Excel into the same format that can be pasted back
  • fix the Designer debug URL (replaces localhost with 127.0.0.1 - that was just lazy of me…)

It would be nice if some of these could be incorporated into the Ignition designer like the UDT usage and nesting and tag compare functions, although a lot of them aren’t for everyone.

Here is her majesty in all her glory :face_vomiting:

2 Likes

That’s a fair question, and you certainly could make something like this in Python. There’s some advantages and disadvantages. For me this was mostly a learning project, so I’ll agree that Python would probably be a better choice for something actually useful. That said, having implemented ‘object oriented structure that converts to specifically formatted JSON’ before in both languages, I genuinely feel like the advantages of Kotlin are worth the hassle in this case. For a simple case; note how in this video trying to add a value to an expression tag is fully disallowed within the IDE - that’s something a dynamically typed language like Python literally can’t do, even if we had a better builtin IDE.

RE: Excel, since this could act as library, you could import Apache POI and use that to parse a given Excel file, then have access to it within this programmatic context where you can generate whatever tags. Or make this run a fixed conversion on the command line, so you pass in plain text/CSV/TSV and it’ll output well-formatted JSON you can pipe to a file.

These would be relatively trivial to do with this code, since it can parse an abitrary tag export into a datastructure - and again, Kotlin’s syntax comes in very handy here to do strictly-typed walks through your different nodes.

This would also be a pretty easy mapping change to make; this is my first stab at it - a more general visitor implementation would be easy to make:

    val provider = TAG_JSON.decodeFromString(Provider.serializer(), rawJson)

    fun Tag.visitChildren(): Tag {
        return when (this) {
            is AtomicTag -> copy(
                valueSource = if (valueSource == AtomicTag.ValueSource.OPC) {
                    AtomicTag.ValueSource.Memory
                } else {
                    valueSource
                }
            )
            is Folder -> copy(tags = this.tags.map(Tag::visitChildren))
            is UDTDef -> copy(tags = this.tags.map(Tag::visitChildren))
            is UDTInstance -> copy(tags = this.tags?.map(Tag::visitChildren))
            is Provider -> copy(tags = this.tags.map(Tag::visitChildren))
        }
    }

This also comes ‘for free’ with this code - since you can (theoretically) import any arbitrary tag export, you could simply do that twice, then deep-compare the Kotlin data structures you get as a result; automatically order agnostic.

But! I’ll agree that there’s definitely room for some of this stuff in the product. I wish we did a better job of ‘exposing’ meta-programming capabilities - the fact it’s hard to do inside the designer is also part of the reason I didn’t pursue writing this in Python.

2 Likes

That actually looks pretty cool! Maybe i’ll have to have a look after all :slight_smile:
I see Kotlin doesn’t have a GUI, but this looks interesting:

Lots of our SCADA devs don’t like to get their hands dirty in coding, so a GUI is definitely a must-have for anything that I dev for SCADA dev purposes.

Down the rabbit hole again… haha. I’ll come back later on when I have more time and have a better read of your reply. Cheers!

Kotlin is just another JVM language. You can use Swing or JavaFX for the GUI. TornadoFX is just a Kotlin DSL for JavaFX.

2 Likes

This looks like exactly the kind of thing that would help with bulk tag edits and easy enough for junior engineers to use. @nminchin is this available publicly also?

Unfortunately not, sorry. It’s also not very production ready, I shared more for ideas about the sorts of tools that a dev might need in the hopes that maybe one day, at least some of the more generic ones might see the light of day in the product in some form or another!

I understand. Keep up the good work and I wish you success.

Pretty much pointless GUI proof-of-concept is now implemented:

1 Like

Just need an icon now haha. The Azul Java logo really needs an overhaul…
I’m keen to have a look at the source, just need some time!