diff --git a/nsfarm/board/_board.py b/nsfarm/board/_board.py
index 178959ed5fef514b8471678b597e69d4261e6f06..a8c974d6240ec1a585fa8e44c8e3b6594009bc1e 100644
--- a/nsfarm/board/_board.py
+++ b/nsfarm/board/_board.py
@@ -1,6 +1,7 @@
 """This defines generic board and its helpers.
 """
 import sys
+import abc
 import time
 import logging
 import serial
@@ -14,7 +15,7 @@ MINITERM_DEFAULT_MENU = '\x14'  # Ctrl+T
 MINITERM_ENCODING = sys.getdefaultencoding()
 
 
-class Board:
+class Board(abc.ABC):
     """General abstract class defining handle for board.
     """
 
@@ -62,10 +63,11 @@ class Board:
         self._pexpect.sendline("")
         return cli.Uboot(self._pexpect)
 
-    def bootup(self, device_wan):
+    def bootup(self, device_wan, os_branch):
         """Boot board using TFTP boot. This ensures that board is booted up and ready to accept commands.
 
         device_wan: Wan device to board. This is instance of nsfarm.lxd.NetInterface.
+        os_branch: Turris OS branch to download medkit from.
 
         Returns instance of cli.Shell
         """
@@ -74,7 +76,7 @@ class Board:
         # Now load image from TFTP
         with Container("boot", devices=[device_wan, ]) as cont:
             ccli = cli.Shell(cont.pexpect())
-            ccli.run("prepare_turris_image")
+            ccli.run(f"prepare_turris_image {os_branch}")
             uboot.run('setenv ipaddr 192.168.1.142')
             uboot.run('setenv serverip 192.168.1.1')
             self._board_bootup(uboot)
@@ -85,12 +87,12 @@ class Board:
         shell.run("sysctl -w kernel.printk='0 4 1 7'")  # disable kernel print to not confuse console flow
         return shell
 
+    @abc.abstractmethod
     def _board_bootup(self, uboot):
         """Board specific bootup routine.
 
         It has to implement TFTP uboot routines.
         """
-        raise NotImplementedError
 
     def serial_miniterm(self):
         """Runs interactive miniterm on serial TTY interface.
@@ -116,26 +118,25 @@ class Board:
 
         sys.stderr.write("\n--- Miniterm exit ---\n")
 
-    @property
+    @abc.abstractproperty
     def wan(self):
         """Network interface name for WAN interface in router
         """
-        raise NotImplementedError
 
-    @property
+    @abc.abstractproperty
     def lan1(self):
         """Network interface name for LAN1 interface in router
+        Note that this not matches lan1 port on router but rather is primary lan port.
         """
-        raise NotImplementedError
 
-    @property
+    @abc.abstractproperty
     def lan2(self):
         """Network interface name for LAN2 interface in router
+        Note that this not matches lan2 port on router but rather is secondary test port (such as different switch).
         """
-        raise NotImplementedError
 
-    @property
+    @abc.abstractproperty
     def lan3(self):
         """Network interface name for LAN3 interface in router
+        Note that this not matches lan3 port on router but rather is tercial test port (such as different switch).
         """
-        raise NotImplementedError
diff --git a/nsfarm/board/omnia.py b/nsfarm/board/omnia.py
index 8e4572b653594bf0c29f9f887d12c059ce941e37..8c736e21b5e7a8953066b7fb07474a2a632ec15c 100644
--- a/nsfarm/board/omnia.py
+++ b/nsfarm/board/omnia.py
@@ -25,3 +25,7 @@ class Omnia(Board):
     @property
     def lan2(self):
         return "lan3"
+
+    @property
+    def lan3(self):
+        return "lan4"
diff --git a/nsfarm/cli.py b/nsfarm/cli.py
index f1ebdd6fb29e3a1f8d40154db22eea0b43d32c6b..e934cd1fa8bd14b6b842d237a9e294370b950ace 100644
--- a/nsfarm/cli.py
+++ b/nsfarm/cli.py
@@ -8,6 +8,7 @@ support for shell and u-boot.  They differ in a way how they handle prompt and m
 # There are new line character matches in regular expressions. Correct one is \r\n but some serial controlles for some
 # reason also use \n\r so we match both alternatives.
 #
+import abc
 import logging
 import base64
 
@@ -31,7 +32,7 @@ def run_exit_code_zero(exit_code):
     return 0
 
 
-class Cli:
+class Cli(abc.ABC):
     """This is generic abstraction on top of pexpect for command line interface.
     """
 
@@ -44,20 +45,19 @@ class Cli:
         # Just propagate anything we do not implement to pexect handle
         return getattr(self._pe, name)
 
+    @abc.abstractmethod
     def prompt(self, **kwargs):
         """Follow output until prompt is reached and parse it.  Exit code is returned.
         All keyword arguments are passed to pexpect's expect call.
         """
-        raise NotImplementedError
 
-    @property
+    @abc.abstractproperty
     def output(self):
         """All output before latest prompt.
 
         This is everything not matched till prompt is located.  Note that this is for some implementations same as
         pexpect before but in others it can differ so you should always use this property instead of before.
         """
-        raise NotImplementedError
 
     def command(self, cmd=""):
         """Calls pexpect sendline and expect cmd with trailing new line.
diff --git a/nsfarm/lxd/device.py b/nsfarm/lxd/device.py
index cd680fe469327e38376d47b9c5b52275481bdc8f..32b87505f63c883db955793fb227b673fb4e64e9 100644
--- a/nsfarm/lxd/device.py
+++ b/nsfarm/lxd/device.py
@@ -1,8 +1,9 @@
 """Devices management and assigment to containers.
 """
+import abc
 
 
-class Device:
+class Device(abc.ABC):
     """Generic device handler for LXD container.
 
     This is device handler that can be assigned to containers. Note that it can be assigned to only one container at the
@@ -34,11 +35,11 @@ class Device:
             self._assignment[-2].unfreeze()
         self._assignment.remove(container)
 
+    @abc.abstractmethod
     def _definition(self):
         """This method has to be implemented by child class and should return definition of device that is later
         provided to caller as result of calling acquire().
         """
-        raise NotImplementedError
 
     @property
     def container(self):