diff --git a/pyskom/client/async_types.py b/pyskom/client/async_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b3eec2b58b46f2f9f69014481e1e54353c4ac51
--- /dev/null
+++ b/pyskom/client/async_types.py
@@ -0,0 +1,376 @@
+# This file was generated at 2025-02-14 19:54:32 by tools.codegen
+# Generated from Protocol A 11.1
+# Run `python -m tools.codegen` with project root as CWD to regenerate
+# For your own sake please do not edit this file as it will be overwritten
+
+from dataclasses import dataclass
+from enum import Enum
+from typing import Union, Self, Any, ClassVar
+
+from .kom_types import KomType, SimpleType
+from .simple_types import KomInt8, KomInt16, KomInt32, KomStr, KomBool,KomFloat
+from .parser import MessageParser
+from .writer import MessageWriter
+
+
+from .types import (
+    TextNo,
+    WhoInfo,
+    AuxItem,
+    SessionNo,
+    InfoType,
+    TextStat,
+    ConfNo,
+    PersNo
+)
+
+@dataclass(slots=True)
+class AsyncNewName:
+    conf_no: 'ConfNo'
+    old_name: KomStr
+    new_name: KomStr
+    message_nr: ClassVar[int] = 5
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.conf_no)
+        writer.write_str(self.old_name)
+        writer.write_str(self.new_name)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_str(),
+            parser.read_str()
+        )
+
+
+@dataclass(slots=True)
+class AsyncIAmOn:
+    info: 'WhoInfo'
+    message_nr: ClassVar[int] = 6
+
+    def write(self, writer: MessageWriter):
+        self.info.write(writer)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            WhoInfo.parse(parser)
+        )
+
+
+@dataclass(slots=True)
+class AsyncSyncDb:
+    message_nr: ClassVar[int] = 7
+
+    def write(self, writer: MessageWriter):
+        pass
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls()
+
+
+@dataclass(slots=True)
+class AsyncLeaveConf:
+    conf_no: 'ConfNo'
+    message_nr: ClassVar[int] = 8
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.conf_no)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16()
+        )
+
+
+@dataclass(slots=True)
+class AsyncLogin:
+    pers_no: 'PersNo'
+    session_no: 'SessionNo'
+    message_nr: ClassVar[int] = 9
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.pers_no)
+        writer.write_int(self.session_no)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int32()
+        )
+
+
+@dataclass(slots=True)
+class AsyncRejectedConnection:
+    message_nr: ClassVar[int] = 11
+
+    def write(self, writer: MessageWriter):
+        pass
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls()
+
+
+@dataclass(slots=True)
+class AsyncSendMessage:
+    recipient: 'ConfNo'
+    sender: 'PersNo'
+    message: KomStr
+    message_nr: ClassVar[int] = 12
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.recipient)
+        writer.write_int(self.sender)
+        writer.write_str(self.message)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int16(),
+            parser.read_str()
+        )
+
+
+@dataclass(slots=True)
+class AsyncLogout:
+    pers_no: 'PersNo'
+    session_no: 'SessionNo'
+    message_nr: ClassVar[int] = 13
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.pers_no)
+        writer.write_int(self.session_no)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int32()
+        )
+
+
+@dataclass(slots=True)
+class AsyncDeletedText:
+    text_no: 'TextNo'
+    text_stat: 'TextStat'
+    message_nr: ClassVar[int] = 14
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.text_no)
+        self.text_stat.write(writer)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int32(),
+            TextStat.parse(parser)
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewText:
+    text_no: 'TextNo'
+    text_stat: 'TextStat'
+    message_nr: ClassVar[int] = 15
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.text_no)
+        self.text_stat.write(writer)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int32(),
+            TextStat.parse(parser)
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewRecipient:
+    text_no: 'TextNo'
+    conf_no: 'ConfNo'
+    type: 'InfoType'
+    message_nr: ClassVar[int] = 16
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.text_no)
+        writer.write_int(self.conf_no)
+        self.type.write(writer)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int32(),
+            parser.read_int16(),
+            InfoType.parse(parser)
+        )
+
+
+@dataclass(slots=True)
+class AsyncSubRecipient:
+    text_no: 'TextNo'
+    conf_no: 'ConfNo'
+    type: 'InfoType'
+    message_nr: ClassVar[int] = 17
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.text_no)
+        writer.write_int(self.conf_no)
+        self.type.write(writer)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int32(),
+            parser.read_int16(),
+            InfoType.parse(parser)
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewMembership:
+    pers_no: 'PersNo'
+    conf_no: 'ConfNo'
+    message_nr: ClassVar[int] = 18
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.pers_no)
+        writer.write_int(self.conf_no)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int16()
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewUserArea:
+    pers_no: 'PersNo'
+    old_user_area: 'TextNo'
+    new_user_area: 'TextNo'
+    message_nr: ClassVar[int] = 19
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.pers_no)
+        writer.write_int(self.old_user_area)
+        writer.write_int(self.new_user_area)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int32(),
+            parser.read_int32()
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewPresentation:
+    conf_no: 'ConfNo'
+    old_presentation: 'TextNo'
+    new_presentation: 'TextNo'
+    message_nr: ClassVar[int] = 20
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.conf_no)
+        writer.write_int(self.old_presentation)
+        writer.write_int(self.new_presentation)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int32(),
+            parser.read_int32()
+        )
+
+
+@dataclass(slots=True)
+class AsyncNewMotd:
+    conf_no: 'ConfNo'
+    old_motd: 'TextNo'
+    new_motd: 'TextNo'
+    message_nr: ClassVar[int] = 21
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.conf_no)
+        writer.write_int(self.old_motd)
+        writer.write_int(self.new_motd)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int16(),
+            parser.read_int32(),
+            parser.read_int32()
+        )
+
+
+@dataclass(slots=True)
+class AsyncTextAuxChanged:
+    text_no: 'TextNo'
+    deleted: list['AuxItem']
+    added: list['AuxItem']
+    message_nr: ClassVar[int] = 22
+
+    def write(self, writer: MessageWriter):
+        writer.write_int(self.text_no)
+        writer.write_array_type(self.deleted)
+        writer.write_array_type(self.added)
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+        return cls(
+            parser.read_int32(),
+            parser.read_array_type(AuxItem.parse),
+            parser.read_array_type(AuxItem.parse)
+        )
+
+
+ASYNC_TYPES = {
+    5: AsyncNewName,
+    6: AsyncIAmOn,
+    7: AsyncSyncDb,
+    8: AsyncLeaveConf,
+    9: AsyncLogin,
+    11: AsyncRejectedConnection,
+    12: AsyncSendMessage,
+    13: AsyncLogout,
+    14: AsyncDeletedText,
+    15: AsyncNewText,
+    16: AsyncNewRecipient,
+    17: AsyncSubRecipient,
+    18: AsyncNewMembership,
+    19: AsyncNewUserArea,
+    20: AsyncNewPresentation,
+    21: AsyncNewMotd,
+    22: AsyncTextAuxChanged
+}
+
+class AsyncMessageNr:
+    NEW_NAME = 5
+    I_AM_ON = 6
+    SYNC_DB = 7
+    LEAVE_CONF = 8
+    LOGIN = 9
+    REJECTED_CONNECTION = 11
+    SEND_MESSAGE = 12
+    LOGOUT = 13
+    DELETED_TEXT = 14
+    NEW_TEXT = 15
+    NEW_RECIPIENT = 16
+    SUB_RECIPIENT = 17
+    NEW_MEMBERSHIP = 18
+    NEW_USER_AREA = 19
+    NEW_PRESENTATION = 20
+    NEW_MOTD = 21
+    TEXT_AUX_CHANGED = 22
\ No newline at end of file
diff --git a/pyskom/client/client.py b/pyskom/client/client.py
index 72176e894a9169548c0d2d4ac20fc244c5e853be..09b8437ddf9ecfb46d2299fae383c1dd9c8a0656 100644
--- a/pyskom/client/client.py
+++ b/pyskom/client/client.py
@@ -1,4 +1,4 @@
-# This file was generated at 2025-02-14 13:23:36 by tools.codegen
+# This file was generated at 2025-02-14 19:54:32 by tools.codegen
 # Generated from 11.1
 # Run `python -m tools.codegen` with project root as CWD to regenerate
 # For your own sake please do not edit this file as it will be overwritten
diff --git a/pyskom/client/client_base.py b/pyskom/client/client_base.py
index a4a9fda3467b85914ff1cf5622b5556d8f9effbc..cd18c2f07d5db857572d33d8da6d1162a27f3763 100644
--- a/pyskom/client/client_base.py
+++ b/pyskom/client/client_base.py
@@ -4,18 +4,21 @@ from asyncio import StreamReader, StreamWriter, Future
 from logging import getLogger
 from typing import Any, Callable, Awaitable
 
+from .async_types import ASYNC_TYPES
 from .errors import ERRORS
 from .kom_error import InvalidErrorCodeError
 from .parser import MessageParser, parse_kom_type
 from .types import Info, ConfZInfo, Conference, ConfType
 from .writer import to_hollerith, MessageWriter, write_kom_type
-from .kom_types import KomType, SimpleType, KomTypeGroup
+from .kom_types import KomType, SimpleType, KomTypeGroup, AsyncMessage
 
 logger = getLogger("client")
 
+
 class CallTimeoutError(Exception):
     pass
 
+
 class ClientBase:
     def __init__(self, host: str, connection_info: str, port: int = 4894):
         self.host: str = host
@@ -28,10 +31,8 @@ class ClientBase:
         self.server_encoding = "iso-8859-1"
         self._next_ref = 0
         self._calls: dict[int, tuple[Future, Callable[[MessageParser | None], Any]]] = {}
-        self._on_connected_hooks: list[Callable[[], Awaitable[None]]] = []
-
-    def on_connected(self, f: Callable[[], Awaitable[None]]):
-        self._on_connected_hooks.append(f)
+        self._on_connected_hook: Callable[[], Awaitable[None]] | None = None
+        self._on_event_hook: Callable[[AsyncMessage], Awaitable[None]] | None = None
 
     async def on_packet(self, packet: bytes):
         parser = MessageParser(packet, cursor=0)
@@ -39,16 +40,16 @@ class ClientBase:
             reply_type = parser.read_reply()
             if reply_type is None:
                 break
-            elif reply_type == 61: # = (result ok)
+            elif reply_type == 61:  # = (result ok)
                 ref = parser.read_int()
-                if (call:= self._calls.pop(ref, None)) is None:
+                if (call := self._calls.pop(ref, None)) is None:
                     logger.warning("Unknown ref number received, discarding")
                     return
                 if call[1] is not None:
                     call[0].set_result(call[1](parser))
                 else:
                     call[0].set_result(None)
-            elif reply_type == 37: # % (Error)
+            elif reply_type == 37:  # % (Error)
                 ref = parser.read_int()
                 if (call := self._calls.pop(ref, None)) is None:
                     logger.warning("Unknown ref number received, discarding")
@@ -63,12 +64,20 @@ class ClientBase:
 
                 call[1].set_exception(error_type(ref, error_status))
             elif reply_type == 58:
-                # Async
-                break
+                _ = parser.read_int()
+                async_type_nr = parser.read_int()
+                if (async_type := ASYNC_TYPES.get(async_type_nr)) is None:
+                    logger.warning(f"Received unknown async-nr: {async_type_nr!r}")
+                    return
+                message = async_type.parse(parser)
+                if self._on_event_hook is not None:
+                    await self._on_event_hook(message)
+
             else:
                 logger.warning(fr"Unable to parse packet {packet!r}")
 
-    async def send_request(self, call_nr: int, args: bytearray | None, parse_callable: Callable[[MessageParser], Any] | None) -> Any:
+    async def send_request(self, call_nr: int, args: bytearray | None,
+                           parse_callable: Callable[[MessageParser], Any] | None) -> Any:
         ref = self._next_ref
         self._next_ref += 1
         message = bytearray(f"{ref} {call_nr}".encode(self.server_encoding))
@@ -112,7 +121,6 @@ class ClientBase:
             logger.error("OSError")
             return
 
-
         self.writer.write(f"A{to_hollerith(self.connection_info)}\n".encode(self.server_encoding))
         await self.writer.drain()
         response = await self.reader.read(1024)
@@ -124,8 +132,8 @@ class ClientBase:
             logger.error("Got non LysKOM response from connection request")
             return
 
-        if len(self._on_connected_hooks) > 0:
-            asyncio.ensure_future(asyncio.gather(*(f() for f in self._on_connected_hooks)))
+        if self._on_connected_hook is not None:
+            asyncio.ensure_future(self._on_connected_hook())
 
         while self.alive:
             data = await self.reader.read(1024)
@@ -133,3 +141,9 @@ class ClientBase:
                 self.alive = False
                 break
             asyncio.ensure_future(self.on_packet(data))  # This might not be perfect
+
+    def on_async(self, f: Callable[[AsyncMessage], Awaitable[None]]):
+        self._on_event_hook = f
+
+    def on_connected(self, f: Callable[[], Awaitable[None]]):
+        self._on_connected_hook = f
diff --git a/pyskom/client/kom_types.py b/pyskom/client/kom_types.py
index aaa3026b857b2cfa0c55e1a60c25ad86176753cd..fc07369bf403a826dd5636a009f24f03d1e1e653 100644
--- a/pyskom/client/kom_types.py
+++ b/pyskom/client/kom_types.py
@@ -1,6 +1,5 @@
 from enum import Enum
-from typing import Any, Self, TYPE_CHECKING, Protocol
-
+from typing import Any, Self, TYPE_CHECKING, Protocol, ClassVar
 
 if TYPE_CHECKING:
     from .parser import MessageParser
@@ -32,6 +31,15 @@ class KomType(Protocol):
     def parse(cls, parser: 'MessageParser') -> Self:
         pass
 
+class AsyncMessage(Protocol):
+    message_pk: ClassVar[int] = -1
+    def write(self, writer: 'MessageWriter'):
+        pass
+
+    @classmethod
+    def parse(cls, parser: 'MessageParser') -> Self:
+        pass
+
 # type KomType = tuple[int, Any]
 type StructMembers = list[tuple[str, KomType]]
 type EnumMembers = dict[str, Any]
diff --git a/pyskom/client/shell/client_shell.py b/pyskom/client/shell/client_shell.py
index 63d86ac4596410e17c65a69a21269e86283e2436..11f726a22693476287f3126e9cd8d2076ad30297 100644
--- a/pyskom/client/shell/client_shell.py
+++ b/pyskom/client/shell/client_shell.py
@@ -14,6 +14,7 @@ from prompt_toolkit.patch_stdout import patch_stdout
 
 from ..client import Client
 from ..kom_error import KomError
+from ..kom_types import AsyncMessage
 from ..types import MiscInfo
 
 
@@ -55,6 +56,7 @@ class RepPysKomClient:
         self.command_path_names = ["action", "subaction"]
         self.parser = self.create_parser()
         self.client: Client = client
+        self.client.on_async(self.on_async)
         super().__init__()
 
     async def execute_command(self, parts):
@@ -77,6 +79,9 @@ class RepPysKomClient:
         except KomError as e:
             print(f"Received KomError: {e!r}")
 
+    async def on_async(self, message: AsyncMessage):
+        print(message)
+
     @command(("help", "?"))
     async def cmd_help(self, args: Namespace):
         self.parser.print_help()
diff --git a/pyskom/client/types.py b/pyskom/client/types.py
index 0c82725ee74d0eac0a42090cc6eddc7cc8a1b737..6572c2de6570108112233e8534d1bf648de79d2f 100644
--- a/pyskom/client/types.py
+++ b/pyskom/client/types.py
@@ -1,4 +1,4 @@
-# This file was generated at 2025-02-14 13:23:36 by tools.codegen
+# This file was generated at 2025-02-14 19:54:32 by tools.codegen
 # Generated from Protocol A 11.1
 # Run `python -m tools.codegen` with project root as CWD to regenerate
 # For your own sake please do not edit this file as it will be overwritten
diff --git a/pyskom/pyskom.py b/pyskom/pyskom.py
index 7ba84ec0611a5fc938bff4d35db849279be3594f..cb28476481ea6da470e6ced409abb3b0ba4e4025 100644
--- a/pyskom/pyskom.py
+++ b/pyskom/pyskom.py
@@ -1,8 +1,11 @@
 import asyncio
 from dataclasses import dataclass
+from typing import Any, Iterable
 
 from .__about__ import __version__
+from .client.async_types import AsyncLogin, AsyncMessageNr
 from .client.client import Client
+from .client.kom_types import AsyncMessage
 
 
 @dataclass(kw_only=True)
@@ -10,14 +13,17 @@ class ConnectionSettings:
     user_host: tuple[str, str] | None = None
     use_utc: bool = True  # set-connection-time-format [120]
     client_name_version: tuple[str, str] = ("PysKOM", __version__)
+    async_messages: Iterable[int] | None = (AsyncMessageNr.LOGIN,)
 
-def get_connection_info(user_host: tuple[str,str] | None):
+
+def get_connection_info(user_host: tuple[str, str] | None):
     if user_host is None:
         import getpass
         import platform
-        user_host = [getpass.getuser(),platform.node()]
+        user_host = [getpass.getuser(), platform.node()]
     return f"{user_host[0]} % {user_host[1]}"
 
+
 class PysKom:
     def __init__(self, username: str, password: str, host: str, port: int = 4894,
                  connection_settings: ConnectionSettings | None = None):
@@ -28,28 +34,40 @@ class PysKom:
         self.connection_settings = connection_settings
 
         self.client = Client(host, get_connection_info(connection_settings.user_host), port)
-        self.client.on_connected(self.on_connected)
+        self.client.on_connected(self._on_connected)
+        self.client.on_async(self._on_async)
 
     async def run(self):
         await self.client.connect()
 
     async def _set_connection_info(self):
-        await asyncio.gather(
+        tasks = [
             self.client.set_connection_time_format(self.connection_settings.use_utc),
             self.client.set_client_version(*self.connection_settings.client_name_version)
-        )
+        ]
+        if self.connection_settings.async_messages is not None:
+            tasks.append(self.client.accept_async(list(self.connection_settings.async_messages)))
+        await asyncio.gather(*tasks)
 
     async def _login(self):
         users = await self.client.lookup_z_name(self.username, True, False)
         if len(users) != 1:
-            raise Exception("Unable to find single user") # TODO
+            raise Exception("Unable to find single user")  # TODO
         user = users[0]
-        await self.client.login(user.conf_no, self.password, False) # TODO invisible
+        await self.client.login(user.conf_no, self.password, False)  # TODO invisible
+        await self.on_login(user.name,user.conf_no)
 
+    async def _on_async(self, message: Any):
+        match message.message_nr:
+            case AsyncMessageNr.LOGIN:
+                await self.event_on_login(message.pers_no, message.session_no)
 
-    async def on_connected(self):
+    async def _on_connected(self):
         await self._set_connection_info()
         await self._login()
 
-    async def on_login(self):
+    async def on_login(self, name: str, pers_no: int):
+        pass
+
+    async def event_on_login(self, pers_no: int, conf_no: int):
         pass
diff --git a/tools/codegen/ast.py b/tools/codegen/ast.py
index b3427fb0aa311f9ae9a3ff3b04bdb03eadfc86a6..c8de77d0bc581ba267c898835fd557d18aff8a0b 100644
--- a/tools/codegen/ast.py
+++ b/tools/codegen/ast.py
@@ -12,6 +12,12 @@ class KomCall:
     call_args: list[tuple[str,KomTypeRef]]
     call_return: KomTypeRef | None
 
+@dataclass
+class KomAsyncMessage:
+    message_nr: int
+    message_name: str
+    message_args: list[tuple[str, KomTypeRef]]
+
 class ProtocolSpec:
     def __init__(self):
         self.definitions: dict[str, KomType] = {}
@@ -19,6 +25,9 @@ class ProtocolSpec:
         self.calls: list[KomCall] = []
         self.aliases = {}
         self.props = {}
+        self.async_messages: list[KomAsyncMessage] = []
+        # self.async_data = []
+
 
     def finalize(self):
         for i in range(len(self.definitions)):
@@ -30,6 +39,7 @@ class ProtocolSpec:
             self.definitions[d.name] = resolved
             self.definitions_ordered[i] = resolved
         self.aliases = {d.name: d.target for d in self.definitions_ordered if isinstance(d, KomAliasType)}
+        self.async_messages.sort(key=lambda x:x.message_nr)
 
     def resolve_aliases(self, name):
         return self.aliases.get(name)
diff --git a/tools/codegen/async_gen.py b/tools/codegen/async_gen.py
new file mode 100644
index 0000000000000000000000000000000000000000..752bcea73ad0c7da5a2b1c2e71c5fc854daf13d0
--- /dev/null
+++ b/tools/codegen/async_gen.py
@@ -0,0 +1,83 @@
+
+from .ast import ProtocolSpec, AsyncNode, KomAsyncMessage
+from .util import to_python_class_name, to_python_var_name, type_ref_to_python_type, type_ref_to_write_code, \
+    type_ref_to_parse_code, codegen_header, get_type_import, to_python_const_name
+
+
+def gen_async_file(spec: ProtocolSpec):
+    parts = [
+        codegen_header("Protocol A " + spec.props.get("PROTOEDITION", "[UNKNOWN]")),
+        FILE_HEAD
+    ]
+
+    async_types_lines = []
+    async_nr_lines = []
+
+    used_types = set()
+    for message in spec.async_messages:
+        text, part_types = gen_async_type(spec, message)
+        parts.append(text)
+        used_types.update(part_types)
+        async_types_lines.append(f"    {message.message_nr}: {to_python_class_name(message.message_name)}")
+        async_nr_lines.append(f"    {to_python_const_name(message.message_name).replace('ASYNC_','')} = {message.message_nr}")
+
+    import_lines = ",\n    ".join(used_types)
+    import_part = f"from .types import (\n    {import_lines}\n)"
+    parts.insert(2, import_part)
+
+    parts.append(f"ASYNC_TYPES = {{\n{',\n'.join(async_types_lines)}\n}}")
+    parts.append(f"class AsyncMessageNr:\n{'\n'.join(async_nr_lines)}")
+
+    return "\n\n".join(parts)
+
+def gen_async_type(spec: ProtocolSpec, message: KomAsyncMessage) -> tuple[str, set[str]]:
+    member_lines = []
+    class_name = to_python_class_name(message.message_name)
+
+    used_types = set()
+
+    if len(message.message_args):
+        parse_members_lines = []
+        write_members_lines = []
+        for name, member_type in message.message_args:
+            if (used_type := get_type_import(member_type)) is not None:
+                used_types.add(used_type)
+
+            var_name = to_python_var_name(name)
+            member_lines.append(f"    {var_name}: {type_ref_to_python_type(member_type)}")
+            write_members_lines.append(f"        {type_ref_to_write_code(spec, member_type, f'self.{var_name}')}")
+            parse_members_lines.append(f"            {type_ref_to_parse_code(spec, member_type)}")
+        parse_lines = "        return cls(\n" + ",\n".join(parse_members_lines) + "\n        )"
+    else:
+        parse_lines = "        return cls()"
+        write_members_lines = ["        pass"]
+    member_lines.append(f"    message_nr: ClassVar[int] = {message.message_nr}")
+    return ASYNC_TEMPLATE.format(
+        class_name=class_name,
+        members="\n".join(member_lines),
+        parse_lines=parse_lines,
+        write_lines = "\n".join(write_members_lines)
+    ), used_types
+
+
+FILE_HEAD = """from dataclasses import dataclass
+from enum import Enum
+from typing import Union, Self, Any, ClassVar
+
+from .kom_types import KomType, SimpleType
+from .simple_types import KomInt8, KomInt16, KomInt32, KomStr, KomBool,KomFloat
+from .parser import MessageParser
+from .writer import MessageWriter
+"""
+ASYNC_TEMPLATE = """@dataclass(slots=True)
+class {class_name}:
+{members}
+
+    def write(self, writer: MessageWriter):
+{write_lines}
+
+    @classmethod
+    def parse(cls, parser: MessageParser) -> Self:
+{parse_lines}
+"""
+
diff --git a/tools/codegen/build_protocol_a.py b/tools/codegen/build_protocol_a.py
index 18be0bfda4cff1d2c9774372a9083b144755548f..a32ff4042fb0bd0d9352dd5536edf37989d394d7 100644
--- a/tools/codegen/build_protocol_a.py
+++ b/tools/codegen/build_protocol_a.py
@@ -1,4 +1,6 @@
-from .ast import DefineNode, parse_define, ProtocolSpec, SetNode, CallNode, KomCall, parse_type_ref
+from .ast import DefineNode, parse_define, ProtocolSpec, SetNode, CallNode, KomCall, parse_type_ref, AsyncNode, \
+    AsyncDataNode, KomAsyncMessage
+from .async_gen import gen_async_file
 from .call_gen import gen_calls_file
 from .kom_types import KomStructType
 from .spec_parser import lexer, parser
@@ -35,6 +37,17 @@ def build_protocol_a(text: str):
                 parse_type_ref(returns) if returns is not None else None
 
             ))
+        elif isinstance(item,  AsyncNode):
+            spec.async_messages.append(KomAsyncMessage(
+                int(item.async_id),
+                item.name,
+                [(arg.name, parse_type_ref(arg.value)) for arg in item.data.get_args()]
+            ))
+        # elif isinstance(item,  AsyncDataNode):
+            # spec.async_data.append(item)
+        else:
+            pass
+            # print(item)
 
             # if isinstance(def_type, KomStructType):
             # print(generate_struct_class(def_type))
@@ -43,5 +56,9 @@ def build_protocol_a(text: str):
     with open("pyskom/client/types.py", "w") as f:
         f.write(gen_types_file(spec))
 
+    with open("pyskom/client/async_types.py", "w") as f:
+        f.write(gen_async_file(spec))
+
     with open("pyskom/client/client.py", "w") as f:
         f.write(gen_calls_file(spec))
+
diff --git a/tools/codegen/util.py b/tools/codegen/util.py
index c1a1db2d529a521b8b04f3479b5a143639ae580f..d3b860b71972bbd7344790c0e7ea25f0ed94e50e 100644
--- a/tools/codegen/util.py
+++ b/tools/codegen/util.py
@@ -19,6 +19,18 @@ def to_python_class_name(name: str):
 def to_python_var_name(name: str):
     return name.replace("-", "_")
 
+def to_python_const_name(name: str):
+    return name.upper().replace("-", "_")
+
+def get_type_import(kom_ref: KomTypeRef) -> str | None:
+    match kom_ref:
+        case (KomTypeRefGroup.TYPE, type_name):
+            return to_python_class_name(type_name)
+        case (KomTypeRefGroup.ARRAY, (KomTypeRefGroup.TYPE, type_name)):
+            return to_python_class_name(type_name)
+        case _:
+            return None
+
 def type_ref_to_python_type(kom_ref: KomTypeRef):
     match kom_ref:
         case (KomTypeRefGroup.SIMPLE, simple_type):
@@ -103,7 +115,6 @@ def type_ref_to_parser_callable(spec: ProtocolSpec, kom_ref: KomTypeRef):
         case (KomTypeRefGroup.ARRAY, (KomTypeRefGroup.SIMPLE, SimpleType.FLOAT)):
             return "parse_kom_float_array"
         case _:
-            print(kom_ref)
             raise Exception("Unknown type")
 
 def type_ref_to_write_code(spec: ProtocolSpec, kom_ref: KomTypeRef, var_name: str):