diff --git a/tools/abb_extctrl_setup.py b/tools/abb_extctrl_setup.py
new file mode 100755
index 0000000000000000000000000000000000000000..e6d021c182ed2b9c16c6155250d8aba0498528dc
--- /dev/null
+++ b/tools/abb_extctrl_setup.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python3
+
+import argparse
+import globalize
+import orcaftp
+import sys
+
+def check_options(parser, options, command, required):
+    options = dict(options._get_kwargs())
+    error = []
+    for r in required:
+        if not r in options:
+            error.append('--%s is not a recognized option' % r)
+            pass
+        elif not options[r]: 
+            error.append('--%s is required for --%s' % (r, command))
+            pass
+    if error:
+        parser.print_help()
+        print('\ninvocation errors:', file=sys.stderr)
+
+        for e in error:
+            print('  %s' % e, file=sys.stderr)
+            pass
+        
+        sys.exit(1)
+        pass
+    pass
+
+if __name__ == '__main__':
+    optParser = argparse.ArgumentParser(usage="%(prog)s [options]")
+    optParser.add_argument('--robot',
+                           required=True,
+                           action='store',
+                           metavar='ROBOT',
+                           help='run actions on ROBOT')
+    optParser.add_argument('--globalize',
+                           action='store',
+                           metavar='C-FILE',
+                           help='globalize IMAGE into C-FILE')
+    optParser.add_argument('--image',
+                           action='store',
+                           metavar='IMAGE')
+    optParser.add_argument('--patterns',
+                           nargs='*',
+                           action='store',
+                           metavar='REGEXP',
+                           help='REGEXP to verify that C-FILE matches IMAGE')
+    optParser.add_argument('--symbols',
+                           nargs='*',
+                           action='store',
+                           metavar='SYMBOL',
+                           help='SYMBOL to globalize')
+    optParser.add_argument('--robotware',
+                           action='store_true',
+                           help='Get robotware version')
+    options = optParser.parse_args(sys.argv[1:])
+
+    robot_ftp = orcaftp.OrcaFTP(options.robot)
+    system = robot_ftp.read_ascii('/hd0a/system.dir').strip()
+    release = robot_ftp.read_ascii('/hd0a/%s/INTERNAL/release.dir' %
+                                   system).strip()
+    if options.globalize:
+        error = []
+        check_options(parser=optParser,
+                      options=options,
+                      command='globalize',
+                      required=[ 'image', 'patterns', 'symbols' ])
+        func = globalize.globalize(ftp=robot_ftp,
+                                   image=options.image,
+                                   patterns=options.patterns,
+                                   symbols=options.symbols,
+                                   robot=options.robot,
+                                   release=release)
+        f = open(options.globalize, 'w')
+        f.write(str(func))
+
+    if options.robotware:
+        print(release)
diff --git a/tools/globalize.py b/tools/globalize.py
new file mode 100644
index 0000000000000000000000000000000000000000..e98c67f182385d1e3fe9fc3dca5f1e6b6be199d7
--- /dev/null
+++ b/tools/globalize.py
@@ -0,0 +1,127 @@
+#!/usr/bin/python3
+
+import argparse
+import globalize
+import orcaftp
+import re
+import subprocess
+import sys
+import tempfile
+import time
+import stringarray
+
+def nm(image):
+    p = subprocess.Popen([ 'nm', image.name ], stdout=subprocess.PIPE)
+    for l in p.stdout:
+        yield l.decode('utf8').split()
+        pass
+    pass
+
+def match(name, patterns):
+    for p in patterns:
+        if re.match(p, name):
+            return True
+    return False
+            
+
+def globalize(ftp, image, patterns, symbols, robot, release):
+    image_file = tempfile.NamedTemporaryFile(
+        prefix='globalize.%s.' % image,
+        suffix='.img')
+    ftp.retrbinary("RETR /hd0a/%s/bin/%s" % (release, image),
+                   image_file.write)
+    image_file.flush()
+        
+    check = []
+    globalize = []
+    for value, kind, name in nm(image_file):
+        if kind.isupper() and match(name, patterns):
+            check.append((value, name))
+            pass
+        if kind.islower and name in symbols:
+            globalize.append((value, name))
+            pass
+        pass
+    result = stringarray.StringArray()
+    result.append_lines("""
+    |char LTH_GLOBALIZE_VERSION[] = 
+    |       "Generated /hd0a/%s/bin/%s.globalize for %s";
+    """ % (release, image.split('.')[0], robot))
+    result.append_lines("""
+    |extern void *sysSymTbl;
+    |extern int symFindByName(
+    |  void *sysSymTbl, 
+    |  char *name,
+    |  void **value,
+    |  int *type);
+    |extern int symAdd(
+    |  void *sysSymTbl, 
+    |  char *name,
+    |  void *value,
+    |  int type,
+    |  short group);
+    |extern int printf(
+    |  char *fmt, ...);
+    |
+    |struct symbols {
+    |  void *value;
+    |  char *name;
+    |};
+    |
+    |static struct symbols to_check[] = {
+    """)
+    for value, name in sorted(check):
+        result += '  { (void*)0x%s, "%s" },' % (value, name)
+        pass
+    result.append_lines("""
+    |  { 0, 0 }
+    |};
+    |
+    |static struct symbols to_globalize[] = {
+    """)
+    for value, name in sorted(globalize):
+        result += '  { (void*)0x%s, "%s" },' % (value, name)
+        pass
+    result.append_lines("""
+    |  { 0, 0 }
+    |};
+    |
+    |int lth_globalize_stage_2()
+    |{
+    |  struct symbols *s;
+    |  int result = 0;
+    |
+    |  for (s = to_check ; s->name ; s++) {
+    |    void *value = 0;
+    |    int type;
+    |    
+    |    if (symFindByName(sysSymTbl, s->name, &value, &type) != 0 ||
+    |	value != s->value) {
+    |      printf("Symbol mismatch '%s' %p != %p\\n", s->name, s->value, value);
+    |      result = 1;
+    |      goto out;
+    |    }
+    |  }
+    |  for (s = to_globalize ; s->name ; s++) {
+    |    void *value = 0;
+    |    int type;
+    |    
+    |    if (symFindByName(sysSymTbl, s->name, &value, &type) == 0) {
+    |      if (value != s->value) {
+    |        printf("Symbol mismatch '%s' %p != %p \\n", 
+    |               s->name, s->value, value);
+    |        result = 1;
+    |        goto out;
+    |      } else {
+    |        printf("Symbol already declared '%s' %p \\n", s->name, s->value);
+    |      }
+    |    } else if (symAdd(sysSymTbl, s->name, s->value, 5, 0) != 0) {
+    |      printf("Symbol declaration failed '%s' %p \\n", s->name, s->value);
+    |      goto out;
+    |    }
+    |  }
+    |out:
+    |  return result;
+    |}
+    """)
+    return result
diff --git a/tools/orcaftp.py b/tools/orcaftp.py
new file mode 100644
index 0000000000000000000000000000000000000000..d06bba901acefb84fd28b92ee968efa474afadc5
--- /dev/null
+++ b/tools/orcaftp.py
@@ -0,0 +1,33 @@
+#!/usr/bin/python3
+
+import ftplib
+import io
+
+class OrcaFTP(ftplib.FTP):
+
+    def __init__(self, host, user=None, passwd=None, acct=None, timeout=None):
+        ftplib.FTP.__init__(self, host, user, passwd, acct, timeout)
+        self.login()
+        pass
+
+    def read_ascii(self, path):
+        result = io.StringIO()
+        self.retrlines("RETR %s" % path, result.write)
+        return result.getvalue()
+    
+
+    def read_binary(self, path):
+        result = io.BytesIO()
+        self.retrbinary("RETR %s" % path, result.write)
+        return result.getvalue()
+
+    def write_ascii(self, path, value):
+        f = io.StringIO(value)
+        self.storlines("STOR %s" % path, f)
+        pass
+
+    def write_binary(self, path, value):
+        f = io.BytesIO(value)
+        self.storbinary("STOR %s" % path, f)
+        pass
+
diff --git a/tools/stringarray.py b/tools/stringarray.py
new file mode 100644
index 0000000000000000000000000000000000000000..c77f2ed183fbc043f512dcc58fc0165686de7473
--- /dev/null
+++ b/tools/stringarray.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python3
+
+class StringArray(list, object):
+
+    def append_lines(self, lines, sep='|'):
+        for l in map(lambda s: s.strip(), lines.split('\n')):
+            if not l:
+                continue
+            if not l.startswith(sep):
+                raise Exception('%s do not start with "%s"' % (l, sep))
+            self.append(l[len(sep):])
+        pass
+    
+    def __iadd__(self, other):
+        try:
+            lines = other.split('\n')
+            if len(lines[-1]) == 0:
+                lines.pop()
+                pass
+            self.extend(lines)
+            pass
+        except AttributeError:
+            self.extend(other)
+            pass
+        return self
+        
+    def indent(self, indent='  '):
+        for l in self:
+            yield '%s%s' % (indent, l)
+            pass
+        pass
+
+    def __str__(self):
+        return '\n'.join(self) + '\n'
+
+    pass
+
+