Script for migrating KNX 1.x configuration to KNX 2.x

Hello everyone,

for helping me migrating my KNX 1.x configuration to the new KNX 2.x binding and avoid “copy & paste” errors I wrote a “quick & dirty” Python script. As others might find this useful as well, I provide it here.

Some things to mention:

  1. The syntax to invoke the script:
migrateKNX.py -o -i migrate/test.items -t migrate/test.things my.items

-o: Includes the old KNX 1.x configuration as comment starting with // KNX 1.x: , so that it can later be easily removed. This is intended to ease debugging the script output
-i: The items file to write the new configuration to (files will be overwritten without warning!)
-t: The things file to write the new configuration to (files will be overwritten without warning!)
my.items: The file containing the old KNX 1.x configuration

  1. This is no “call the script and everything is done solution”. The .things file does not contain any device information or bridge configuration. This has to be added manually. For every existing knx item the channel configuration is generated and the item configuration is adapted to that channel. If you use more than one KNX device in the configuration the “generic” string in the .items and device configuration has to be adapted as well.

  2. Lines from the existing items file, not containing a KNX configuration will be written to the new .items file without modification. After adapting the device configuration manually I could just copy the .items file to my configuration folder.

  3. The script will not work correctly when an item has more than one binding configuration. But in this cases the output can still be used to add the missing parts

  4. The script will most certainly contain bugs. It worked for my configuration, but I can give no guarantee whatsoever, that it will work with your configuration. Make a backup of your existing configuration first, before using the script.

  5. I was not allowed to upload a Python or text file, so I included it as code block. Maybe there is an other way to include the file without messing the Python indention?

  6. I am not a Python specialist, so this script is certainly not a “Python best practices” example :wink:
    Juelicher

#!/usr/bin/python3

from argparse import ArgumentParser
import re


itemNameWidth   = 47 
itemLabelWidth  = 49 
itemIconWidth   = 15 
itemGroupsWidth = 45 
itemTagsWidth   = 17


parser = ArgumentParser()

parser = ArgumentParser(description='Migrate openHAB KNX 1.x configuration to KNX 2.x')
parser.add_argument("-i", "--items",    dest="items",    type=str, default="knx.items.new",    help="File to write newly generated items to")
parser.add_argument("-t", "--things",   dest="things",   type=str, default="knx.things.new",   help="File to write newly generated items to")
parser.add_argument("-o", "--original", dest="original",  action="store_true", default = False, help="Include original configuration in items and things file")
parser.add_argument("input",  type=str, default="my.items",   help="File to read current KNX 1.x configuration from")

args = parser.parse_args()


inFile     = open(args.input, "r")
itemsFile  = open(args.items, "w") 
thingsFile = open(args.things, "w") 

   
    
def removeComments(line):
    #remove line comments 
    return re.sub(r"\/\/.*$", "", line)
    

def hasKNXConfiguration(line):
    return re.match(r"^.*{.*knx\s*=.*}.*$", line)


def getItemType(line):
    itemType = re.match(r"^\s*(Switch)|(Number)|(Dimmer)|(Contact)|(DateTime)|(Rollershutter)|(String)", line).group(0)
    return itemType


def getItemName(line):
    item = re.match(r"^\s*(?P<type>.+?)\s+(?P<name>.+?)\s+", line) 
    return item.group("name")    


def getItemLabel(line):
    itemLabel = ""
    match = re.match(r"[^\[{]*?\s+(?P<label>\".+?\")\s+[^\]}]*", line)
    if (match):
        itemLabel = match.group("label")   
    return itemLabel


def getItemIcon(line):
    itemIcon = ""
    # Remove text inside quotes before matching to avoid matches within the label
    match = re.match(r".*?\s+(?P<icon><.+?>)\s+.*?", re.sub(r"\"(.*?)\"", "", line)) 
    if (match):
        itemIcon = match.group("icon") 
    return itemIcon


def getItemGroup(line):
    itemGroup = ""
    # Remove text inside quotes before matching to avoid matches within the label
    match = re.match(r".*?\s+(?P<group>\(.+?\))\s+.*?", re.sub(r"\"(.*?)\"", "", line))
    if (match):
        itemGroup = match.group("group") 
    return itemGroup        
    

def getItemTags(line):
    itemTags = ""
    match = re.match(r".*?\s+(?P<tags>\[\s+\".*?\"\s+\])\s+.*", line)
    if (match):
        itemTags = match.group("tags") 
    return itemTags    


def getItemKNXConfig(line):
    itemKNXConfig = ""
    match = re.match(r".*?\{\s*knx\s*=\s*\"(?P<knx>.*?)\"\s*,{0,1}.*\s*\}", line)
    if (match):
        itemKNXConfig = match.group("knx") 
    return itemKNXConfig    


def getItemAutoUpdate(line):
    itemAutoUpdate = ""
    match = re.match(r".*?\{.*(?P<auto>autoupdate\s*=\s*\".+\")\s*\}", line)
    if (match):
        itemAutoUpdate = match.group("auto") 
    return itemAutoUpdate    


def getKNXGroupAdress(line):
    KNXGroupAdress = ""
    #<*\d+\/\d+\/\d+
    match = re.match(r"<*\d+\/\d+\/\d+", line)
    if (match):
        KNXGroupAdress = match.group("ga") 
    return itemKNXGroupAdress    



def appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate):
    return name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " " + icon.ljust(itemIconWidth) + groups.ljust(itemGroupsWidth) + tags.ljust(itemTagsWidth) + " { channel=\"knx:device:bridge:generic:" + name + "\" }\n"

def assembleSwitch(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type switch                  : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " [ ga=\"" + KNX + "\" ]\n"
    itemsLine  = "Switch        " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine


def assembleContact(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type contact                 : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " [ ga=\"" + KNX + "\" ]\n"
    itemsLine  = "Contact       " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine
 
def assembleString(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type string                 : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " [ ga=\"" + KNX + "\" ]\n"
    itemsLine  = "String       " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine

def assembleNumber(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type number                  : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " [ ga=\"" + KNX + "\" ]\n"
    itemsLine  = "Number        " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine
    
def assembleDateTime(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type datetime                : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth) + " [ ga=\"" + KNX + "\" ]\n"
    itemsLine  = "DateTime      " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine
 
def assembleDimmer(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type dimmer                  : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth)
    gaList = KNX.split(",")
    if len(gaList) == 3:
        onOff, increaseDecrease, percent = gaList[0].strip(), gaList[1].strip(), gaList[2].strip()
        thingsLine = thingsLine + " [ switch=\"" + onOff +  "\", position=\"" +  percent + "\", increaseDecrease=\"" +    increaseDecrease + "\" ]\n"
    elif len(gaList) == 2:
        onOff, increaseDecrease = gaList[0].strip(), gaList[1].strip()
        thingsLine = thingsLine + " [ switch=\"" + onOff +  "\", increaseDecrease=\"" +    increaseDecrease + "\" ]\n"
    elif len(gaList) == 1:
        percent = gaList[0].strip()
        thingsLine = thingsLine + " [ position=\"" + percent + "\" ]\n"
    itemsLine  = "Dimmer        " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine


def assembleRollerShutter(name, label, icon, groups, tags, KNX, autoupdate):
    thingsLine = "Type rollershutter           : " + name.ljust(itemNameWidth) + " " + label.ljust(itemLabelWidth)
    gaList = KNX.split(",")

    if len(gaList) == 3:
        upDown, stopMove, percent = gaList[0].strip(), gaList[1].strip(), gaList[2].strip()
        thingsLine =  thingsLine + " [ upDown=\"" + upDown +  "\", stopMove=\"" +  stopMove + "\", position=\"" +    percent + "\" ]\n"
    elif len(gaList) == 2:
        upDown, stopMove = gaList[0].strip(), gaList[1].strip()
        thingsLine =  thingsLine + " [ upDown=\"" + upDown +  "\", stopMove=\"" +  stopMove + "\" ]\n"
    
    itemsLine  = "Rollershutter " + appendItemConfig(name, label, icon, groups, tags, KNX, autoupdate)
    return thingsLine, itemsLine



for inputLine in inFile: 
    clearedLine = removeComments(inputLine)
    if (not hasKNXConfiguration(clearedLine)):
        itemsFile.write(inputLine)  # Write lines without KNX configuration unmodified to items file
        continue

    if (args.original):
        itemsFile.write("\n// KNX 1.x: " + inputLine)
        thingsFile.write("\n// KNX 1.x: " + inputLine)

    itemType = getItemType(clearedLine)
    
    newConfig = ""

    if   (itemType == "Switch"): 
        newConfig = assembleSwitch(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "Number"): 
        newConfig = assembleNumber(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "Dimmer"): 
        newConfig = assembleDimmer(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "Contact"): 
        newConfig = assembleContact(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "DateTime"): 
        newConfig = assembleDateTime(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "Rollershutter"): 
        newConfig = assembleRollerShutter(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))
    elif (itemType == "String"): 
        newConfig = assembleString(getItemName(clearedLine), getItemLabel(clearedLine), getItemIcon(clearedLine), getItemGroup(clearedLine), getItemTags(clearedLine), getItemKNXConfig(clearedLine),getItemAutoUpdate(clearedLine))

    if (newConfig != ""):
        thingsFile.write(newConfig[0])       
        itemsFile.write(newConfig[1])
    
    

inFile.close()
itemsFile.close()
thingsFile.close()

6 Likes

Hi @juelicher,

thanks a lot for the script!!
It helped me a lot to migrate all my knx items to the knx2 binding!
A couple of things to mention:

  • the first initial run of the script aborted with an exception due to a Color item in my items file. I commented the Color items, after that it worked for everything.
  • the script did not migrate my ‘rollershutter’ items. I had to add the ‘position’ group addresses by myself.

After I used your script, I just had to

  • add the bridge settings and a generic thing
  • remove the autoRefresh (xx) attributes from the group addresses
    and everything worked!!!

I was really afraid of the migration, but with your script the whole migration was done in like 2 hours… (and I really have a lot of knx items!)

Thanks a lot!

Hi Axel,

thanks for the feedback, great to hear, that the script was usefull for others!

Juelicher

Hi Juelicher,

You’re a lifesaver :smiley: I’ve been holding off upgrading to KNX2 for years. Now that openHAB3 doesn’t support v1 bindings I wanted to bite the bullet and then found your script. Haven’t used it yet, but I’m sure its gonna save me time converting 50+ items :slight_smile:

thanks

1 Like