diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py
index 3c69e86a3cc540b26f87709d8c28fd964e0ebdd2..c02d1af81e18b68d1633916ba7fe9020cd73d984 100644
--- a/ebpfcat/ethercat.py
+++ b/ebpfcat/ethercat.py
@@ -128,8 +128,13 @@ class ObjectDescription:
     def __getitem__(self, idx):
         return self.entries[idx]
 
+    def __repr__(self):
+        return " ".join(f"[{k:X}: {v}]" for k, v in self.entries.items())
+
 
 class ObjectEntry:
+    name = None
+
     def __init__(self, desc):
         self.desc = desc
 
@@ -155,6 +160,13 @@ class ObjectEntry:
         return await self.desc.terminal.sdo_write(d, self.desc.index,
                                                   self.valueInfo)
 
+    def __repr__(self):
+        if self.name is None:
+            return "[unread ObjectEntry]"
+
+        return f'"{self.name}" {self.dataType}:{self.bitLength} ' \
+               f'{self.objectAccess:X}'
+
 
 def datasize(args, data):
     out = calcsize("<" + "".join(arg for arg in args if isinstance(arg, str)))
@@ -221,11 +233,11 @@ class Packet:
         pos = 14 + self.DATAGRAM_HEADER
         ret = []
         for cmd, bits, *dgram in self.data:
-            ret.append((data[pos-self.DATAGRAM_HEADER],
-                        data[pos:pos+len(bits)],
+            ret.append(unpack("<Bxh6x", data[pos-self.DATAGRAM_HEADER:pos])
+                       + (data[pos:pos+len(bits)],
                         unpack("<H", data[pos+len(bits):pos+len(bits)+2])[0]))
             pos += self.DATAGRAM_HEADER + self.DATAGRAM_TAIL
-        return ''.join(f"{i}: {c} {f} {d}\n" for i, (c, d, f) in enumerate(ret))
+        return ''.join(f"{i}: {c} {a} {f} {d}\n" for i, (c, a, d, f) in enumerate(ret))
 
     def full(self):
         """Is the data limit reached?"""
@@ -322,6 +334,7 @@ class EtherCat(Protocol):
             out += b"\0" * data
         elif data is not None:
             out += data
+        assert isinstance(pos, int) and isinstance(offset, int)
         self.send_queue.put_nowait((cmd, out, idx, pos, offset, future))
         ret = await future
         if data is None:
@@ -333,6 +346,14 @@ class EtherCat(Protocol):
         else:
             return ret
 
+    async def count(self):
+        """Count the number of terminals on the bus"""
+        p = Packet()
+        p.append(ECCmd.APRD, b"\0\0", 0, 0, 0x10)
+        ret = await self.roundtrip_packet(p)
+        no, = unpack("<h", ret[16:18])  # number of terminals
+        return no
+
     def connection_made(self, transport):
         """start the send loop once the connection is made"""
         transport.get_extra_info("socket").bind(self.addr)
@@ -473,7 +494,7 @@ class Terminal:
                                         start, *args, **kwargs))
 
     async def write(self, start, *args, **kwargs):
-        """write data from the terminal at offset `start`
+        """write data to the terminal at offset `start`
 
         see `EtherCat.roundtrip` for details on more parameters"""
         return (await self.ec.roundtrip(ECCmd.FPWR, self.position,
@@ -691,55 +712,3 @@ class Terminal:
                 oe.name = data[8:].decode("utf8")
                 od.entries[i] = oe
         return ret
-
-
-async def main():
-    from .bpf import lookup_elem
-
-    ec = EtherCat("eth0")
-    await ec.connect()
-    #map_fd = await install_ebpf2()
-    tin = Terminal()
-    tin.ec = ec
-    tout = Terminal()
-    tout.ec = ec
-    tdigi = Terminal()
-    tdigi.ec = ec
-    await gather(
-        tin.initialize(-4, 19),
-        tout.initialize(-2, 55),
-        tdigi.initialize(0, 22),
-        )
-    print("tin")
-    #await tin.to_operational()
-    await tin.set_state(2)
-    print("tout")
-    await tout.to_operational()
-    print("reading odlist")
-    odlist2, odlist = await gather(tin.read_ODlist(), tout.read_ODlist())
-    #oe = odlist[0x7001][1]
-    #await oe.write(1)
-    for o in odlist.values():
-        print(hex(o.index), o.name, o.maxSub)
-        for i, p in o.entries.items():
-            print("   ", i, p.name, "|", p.dataType, p.bitLength, p.objectAccess)
-            #sdo = await tin.sdo_read(o.index, i)
-            try:
-               sdo = await p.read()
-               if isinstance(sdo, int):
-                   t = hex(sdo)
-               else:
-                   t = ""
-               print("   ", sdo, t)
-            except RuntimeError as e:
-               print("   E", e)
-    print("set sdo")
-    oe = odlist[0x8010][7]
-    print("=", await oe.read())
-    await oe.write(1)
-    print("=", await oe.read())
-    print(tdigi.eeprom[10])
-
-if __name__ == "__main__":
-    loop = get_event_loop()
-    loop.run_until_complete(main())
diff --git a/ebpfcat/scripts.py b/ebpfcat/scripts.py
new file mode 100644
index 0000000000000000000000000000000000000000..7025076d7a413f2d6ff3f078c374583395382f8a
--- /dev/null
+++ b/ebpfcat/scripts.py
@@ -0,0 +1,78 @@
+from argparse import ArgumentParser
+import asyncio
+from functools import wraps
+from struct import unpack
+import sys
+
+from .ethercat import EtherCat, Terminal, ECCmd
+
+def entrypoint(func):
+    @wraps(func)
+    def wrapper():
+        asyncio.run(func())
+    return wrapper
+
+
+@entrypoint
+async def scanbus():
+    ec = EtherCat(sys.argv[1])
+    await ec.connect()
+    no = await ec.count()
+    for i in range(no):
+        r, = await ec.roundtrip(ECCmd.APRD, -i, 0x10, "H", 44)
+        print(i, r)
+
+@entrypoint
+async def info():
+    parser = ArgumentParser(
+        prog = "ec-info",
+        description = "Retrieve information from an EtherCat bus")
+
+    parser.add_argument("interface")
+    parser.add_argument("-t", "--terminal", type=int)
+    parser.add_argument("-i", "--ids", action="store_true")
+    parser.add_argument("-n", "--names", action="store_true")
+    parser.add_argument("-s", "--sdo", action="store_true")
+    parser.add_argument("-v", "--values", action="store_true")
+    args = parser.parse_args()
+
+    ec = EtherCat(args.interface)
+    await ec.connect()
+
+    if args.terminal is None:
+        terminals = range(await ec.count())
+    else:
+#        print(await ec.roundtrip(ECCmd.FPRW, 7, 0x10, "H", 0))
+        terminals = [args.terminal]
+
+    terms = [Terminal() for t in terminals]
+    for t in terms:
+        t.ec = ec
+    await asyncio.gather(*(t.initialize(-i, i + 7)
+                           for i, t in zip(terminals, terms)))
+
+    for i, t in enumerate(terms):
+        print(f"terminal no {i}")
+        if args.ids:
+            print(f"{t.vendorId:X}:{t.productCode:X} "
+                  f"revision {t.revisionNo:X} serial {t.serialNo}")
+        if args.names:
+            infos = t.eeprom[10]
+            i = 1
+            while i < len(infos):
+                print(infos[i+1 : i+infos[i]+1].decode("ascii"))
+                i += infos[i] + 1
+
+        if args.sdo:
+            await t.to_operational()
+            ret = await t.read_ODlist()
+            for k, v in ret.items():
+                print(f"{k:X}:")
+                for kk, vv in v.entries.items():
+                     print(f"    {kk:X}: {vv}")
+                     if args.values:
+                         r = await vv.read()
+                         if isinstance(r, int):
+                             print(f"        {r:10} {r:8X}")
+                         else:
+                             print(f"        {r}")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..995d3a93148669770bc8c9e9a5789b7200803e19
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,8 @@
+[project]
+name = "ebpfcat"
+version = "0.0.1"
+dependencies = []
+
+[project.scripts]
+ec-scanbus = "ebpfcat.scripts:scanbus"
+ec-info = "ebpfcat.scripts:info"