Microsemi and OMCI very initial code

Change-Id: Ieb648f43eab3b2dff50093c79ed27b9e40ae130e
diff --git a/BUILD.md b/BUILD.md
index 718b693..a9b6cc3 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -401,3 +401,24 @@
   
   Unfortunately I was not yet able to resolve this on the Mac.
 
+### Scapy related import issues on MAC OS
+
+ 1. I had issues with "from scapy.all import *". It errored out with import error not finding
+dumbnet. The following resolved the issue:
+
+   ```
+   cd $VOLTHA_BASE
+   . env.sh
+   mkdir tmp
+   cd tmp
+   git clone https://github.com/dugsong/libdnet.git
+   cd libdnet
+   ./configure
+   make
+   sudo make install
+   cd python
+   python setup.py install
+   cd ../..
+   rm -fr tmp
+   ```
+
diff --git a/tests/utests/voltha/__init__.py b/tests/utests/voltha/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/voltha/__init__.py
diff --git a/tests/utests/voltha/adapters/__init__.py b/tests/utests/voltha/adapters/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/voltha/adapters/__init__.py
diff --git a/tests/utests/voltha/adapters/microsemi/__init__.py b/tests/utests/voltha/adapters/microsemi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/voltha/adapters/microsemi/__init__.py
diff --git a/tests/utests/voltha/adapters/microsemi/test_chat.py b/tests/utests/voltha/adapters/microsemi/test_chat.py
new file mode 100644
index 0000000..d0f2afc
--- /dev/null
+++ b/tests/utests/voltha/adapters/microsemi/test_chat.py
@@ -0,0 +1,528 @@
+from unittest import TestCase, main
+
+from voltha.adapters.microsemi.chat import *
+from voltha.extensions.omci.omci import OMCIFrame, OMCIGetRequest, \
+    OMCIGetResponse
+
+
+class TestChat(TestCase):
+
+    def check_gen(self, frames, raw_frames):
+        self.assertEqual(len(frames), len(raw_frames),
+                         "number of frames do not match")
+        for i in range(len(frames)):
+            generated = str(frames[i])
+            expected = raw_frames[i]
+            if generated != expected:
+                print("Mismatch between generated vs expected frame:")
+                print("Generated:")
+                hexdump(generated)
+                print("Expected:")
+                hexdump(expected)
+                self.fail("Mismatch between generated vs expected frame "
+                          "(see above printout)")
+
+    def check_parsed(self, in_raw, reference_msg, channel_id=-1, onu_id=-1,
+                     onu_session_id=-1):
+
+        in_pkt = PAS5211Dot3(in_raw)
+
+        pas5211_header = in_pkt.payload.payload
+        self.assertEqual(pas5211_header.channel_id, channel_id)
+        self.assertEqual(pas5211_header.onu_id, onu_id)
+        self.assertEqual(pas5211_header.onu_session_id, onu_session_id)
+
+        pas5211_msg = in_pkt.payload.payload.payload
+        # so that we ignore junk/padding/fcs/etc after payload
+        pas5211_msg.remove_payload()
+        if pas5211_msg != reference_msg:
+            print("Decoded full packet:")
+            in_pkt.show()
+            hexdump(in_raw)
+            print("Decoded payload message:")
+            pas5211_msg.show()
+            hexdump(str(pas5211_msg))
+            print("Expected message:")
+            reference_msg.show()
+            hexdump(str(reference_msg))
+            self.fail("Decoded message did not match! "
+                      "(inspect above printouts")
+        self.assertEqual(pas5211_msg, reference_msg)
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_get_protocol_version
+
+    def test_get_protocol_version(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgGetProtocolVersion(), 1),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x16\x01\x00'
+                '\x01\x00\x10\x00\xcd\xab4\x12\x01\x00\x00\x00\x020\x00\x00'
+                '\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_get_protocol_version_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00"\x01\x00\x01'
+            '\x00\x18\x00\xcd\xab4\x12\x01\x00\x00\x00\x02(\x00\x00\xff\xff'
+            '\xff\xff\xff\xff\xff\xff\x11R\x02\x00\x10\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u\xd3Y'
+            '\x1d',
+            PAS5211MsgGetProtocolVersionResponse(
+                major_hardware_version=21009,
+                minor_hardware_version=2,
+                major_pfi_version=16,
+                minor_pfi_version=0
+            )
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_get_olt_version
+
+    def test_get_olt_version(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgGetOltVersion(), 1),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x16\x01\x00'
+                '\x01\x00\x10\x00\xcd\xab4\x12\x01\x00\x00\x00\x030\x00\x00'
+                '\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_get_olt_version_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00R\x01\x00\x01'
+            '\x00H\x00\xcd\xab4\x12\x03\x00\x00\x00\x038\x00\x00\xff\xff\xff'
+            '\xff\xff\xff\xff\xff\x02\x00\x03\x009\x00\xea\x03\x11R\x02\x00'
+            '\x00\x00\x00\x00\x04\x00\x80\x00\xff\x0f\x00\x02\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00B\xd7\xeb\xe1',
+            PAS5211MsgGetOltVersionResponse(
+                major_firmware_version=2,
+                minor_firmware_version=3,
+                build_firmware_version=57,
+                maintenance_firmware_version=1002,
+                major_hardware_version=21009,
+                minor_hardware_version=2,
+                system_port_mac_type=PON_MII,
+                channels_supported=4,
+                onus_supported_per_channel=128,
+                ports_supported_per_channel=4095,
+                alloc_ids_supported_per_channel=512,
+                critical_events_counter=[0, 0, 0, 0],
+                non_critical_events_counter=[1, 0, 0, 0]
+            )
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_set_olt_optics
+
+    def test_set_olt_optics(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSetOltOptics(
+                burst_timing_ctrl=BurstTimingCtrl(
+                    snr_burst_delay=SnrBurstDelay(
+                        timer_delay=8,
+                        preamble_delay=32,
+                        delimiter_delay=128,
+                        burst_delay=128),
+                    rng_burst_delay=RngBurstDelay(
+                        timer_delay=8,
+                        preamble_delay=32,
+                        delimiter_delay=128),
+                        # burst_delay=63451),
+                    burst_delay_single=1,
+                    burst_delay_double=1
+                ),
+                general_optics_params=GeneralOpticsParams(
+                    laser_reset_polarity=PON_POLARITY_ACTIVE_HIGH,
+                    laser_sd_polarity=PON_POLARITY_ACTIVE_HIGH,
+                    sd_source=PON_SD_SOURCE_LASER_SD,
+                    sd_hold_snr_ranging=PON_DISABLE,
+                    sd_hold_normal=PON_DISABLE,
+                    reset_type_snr_ranging=PON_RESET_TYPE_DELAY_BASED,
+                    reset_type_normal=PON_RESET_TYPE_NORMAL_START_BURST_BASED,
+                    laser_reset_enable=PON_ENABLE,
+                ),
+                reset_timing_ctrl=ResetTimingCtrl(
+                    reset_data_burst=ResetValues(
+                        bcdr_reset_d2=1,
+                        bcdr_reset_d1 = 11,
+                        laser_reset_d2=2,
+                        laser_reset_d1=5),
+                    reset_snr_burst=ResetValues(
+                        bcdr_reset_d2=2,
+                        bcdr_reset_d1=9,
+                        laser_reset_d2=2,
+                        laser_reset_d1=1),
+                    reset_rng_burst=ResetValues(
+                        bcdr_reset_d2=2,
+                        bcdr_reset_d1=9,
+                        laser_reset_d2=2,
+                        laser_reset_d1=1),
+                    single_reset=ResetValues(
+                        bcdr_reset_d2=1,
+                        bcdr_reset_d1=1,
+                        laser_reset_d2=1,
+                        laser_reset_d1=1),
+                    double_reset=DoubleResetValues(
+                        bcdr_reset_d4=1,
+                        bcdr_reset_d3=1,
+                        laser_reset_d4=1,
+                        laser_reset_d3=1)
+                ),
+                voltage_if_mode=PON_OPTICS_VOLTAGE_IF_LVPECL,
+                preamble_params=PreambleParams(
+                    correlation_preamble_length=8,
+                    preamble_length_snr_rng=119,
+                    guard_time_data_mode=32,
+                    type1_size_data=0,
+                    type2_size_data=0,
+                    type3_size_data=5,
+                    type3_pattern=170,
+                    delimiter_size=20,
+                    delimiter_byte1=171,
+                    delimiter_byte2=89,
+                    delimiter_byte3=131)
+            ), 3, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00V\x01\x00'
+                '\x01\x00P\x00\xcd\xab4\x12\x03\x00\x00\x00j0\x00\x00\x00\x00'
+                '\xff\xff\xff\xff\xff\xff\x08\x00 \x00\x80\x00\x80\x00\x08'
+                '\x00 \x00\x80\x00\x01\x00\x01\x00\x01\x01\x00\x00\x00\x00'
+                '\x00\x01\x00\x00\x00\x01\x0b\x02\x05\x02\t\x02\x01\x02\t\x02'
+                '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x02\x08w \x00\x00\x05'
+                '\xaa\x14\xabY\x83\x00\x00\x00'
+            ]
+        )
+
+    def test_set_olt_optics_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00\x1a\x01\x00'
+            '\x01\x00\x10\x00\xcd\xab4\x12\x03\x00\x00\x00j(\x00\x00\x00\x00'
+            '\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe1?'
+            '\x85\xce',
+            PAS5211MsgSetOltOpticsResponse(),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_set_optics_io_control
+
+    def test_set_optics_io_control(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSetOpticsIoControl(
+                i2c_clk=PON_GPIO_LINE_1,
+                i2c_data=PON_GPIO_LINE_0,
+                tx_enable=PON_GPIO_LINE_6,
+                tx_fault=PON_EXT_GPIO_LINE(6),
+                tx_enable_polarity=PON_POLARITY_ACTIVE_LOW,
+                tx_fault_polarity=PON_POLARITY_ACTIVE_HIGH
+            ), 7, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x1c\x01'
+                '\x00\x01\x00\x16\x00\xcd\xab4\x12\x07\x00\x00\x00l0\x00\x00'
+                '\x00\x00\xff\xff\xff\xff\xff\xff\x01\x00\x06\x0e\x00\x01\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_set_optics_io_control_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00\x1a\x01\x00\x01'
+            '\x00\x10\x00\xcd\xab4\x12\x07\x00\x00\x00l(\x00\x00\x00\x00\xff'
+            '\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc2\x8b\xc4'
+            '\xd7',
+            PAS5211MsgSetOpticsIoControlResponse(),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_get_general_param
+
+    def test_get_general_param(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgGetGeneralParam(
+                parameter=PON_TX_ENABLE_DEFAULT
+            ), 11, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x1e\x01'
+                '\x00\x01\x00\x18\x00\xcd\xab4\x12\x0b\x00\x00\x00\xa50\x00'
+                '\x00\x00\x00\xff\xff\xff\xff\xff\xff\xe9\x03\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_get_general_param_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00&\x01\x00\x01'
+            '\x00\x1c\x00\xcd\xab4\x12\x0b\x00\x00\x00\xa5(\x00\x00\x00\x00'
+            '\xff\xff\xff\xff\xff\xff\xe9\x03\x00\x00\x00\x00\x00\x00\x01\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd6Yf'
+            '\xf9',
+            PAS5211MsgGetGeneralParamResponse(
+                parameter=PON_TX_ENABLE_DEFAULT,
+                value=1
+            ),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_set_general_param
+
+    def test_set_general_param(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSetGeneralParam(
+                parameter=PON_TX_ENABLE_DEFAULT,
+                value=0
+            ), 11, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x22\x01'
+                '\x00\x01\x00\x1c\x00\xcd\xab4\x12\x0b\x00\x00\x00\xa40\x00'
+                '\x00\x00\x00\xff\xff\xff\xff\xff\xff\xe9\x03\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_set_general_param_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00&\x01\x00\x01'
+            '\x00\x1c\x00\xcd\xab4\x12\x0b\x00\x00\x00\xa4(\x00\x00\x00\x00'
+            '\xff\xff\xff\xff\xff\xff\xe9\x03\x00\x00\x00\x00\x00\x00\x01\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd6Yf'
+            '\xf9',
+            PAS5211MsgSetGeneralParamResponse(),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_add_olt_channel
+
+    def test_add_olt_channel(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgAddOltChannel(
+            ), 12, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x16\x01'
+                '\x00\x01\x00\x10\x00\xcd\xab4\x12\x0c\x00\x00\x00\x040\x00'
+                '\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_add_olt_channel_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00\x1a\x01\x00'
+            '\x01\x00\x10\x00\xcd\xab4\x12\x0c\x00\x00\x00\x04(\x00\x00\x00'
+            '\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90L'
+            '\xbf\xdd',
+            PAS5211MsgAddOltChannelResponse(
+            ),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_set_alarm_config
+
+    def test_set_alarm_config(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSetAlarmConfig(
+                type=PON_ALARM_SOFTWARE_ERROR,
+                activate=PON_ENABLE
+            ), 19, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00*\x01\x00'
+                '\x01\x00$\x00\xcd\xab4\x12\x13\x00\x00\x0000\x00\x00\x00\x00'
+                '\xff\xff\xff\xff\xff\xff\x00\x00\x01\x00\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_set_alarm_config_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00\x1a\x01\x00\x01'
+            '\x00\x10\x00\xcd\xab4\x12\x13\x00\x00\x000(\x00\x00\x00\x00\xff'
+            '\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\xc5'
+            '\xf4i',
+            PAS5211MsgSetAlarmConfigResponse(),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_get_dba_mode
+
+    def test_get_dba_mode(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgGetDbaMode(
+            ), 23, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x16\x01'
+                '\x00\x01\x00\x10\x00\xcd\xab4\x12\x17\x00\x00\x0090\x00\x00'
+                '\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_get_dba_mode_response(self):
+        self.check_parsed(
+            'h\x05\xca\x05\xf2\xef\x00\x0c\xd5\x00\x01\x00\x00\x1e\x01\x00\x01'
+            '\x00\x14\x00\xcd\xab4\x12\x17\x00\x00\x009(\x00\x00\x00\x00\xff'
+            '\xff\xff\xff\xff\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x85\xd0?',
+            PAS5211MsgGetDbaModeResponse(
+                dba_mode=PON_DBA_MODE_RUNNING
+            ),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_set_olt_channel_activation_period
+
+    def test_set_olt_channel_activation_period(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSetOltChannelActivationPeriod(
+                activation_period=1000
+            ), 31, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x1a\x01\x00'
+                '\x01\x00\x14\x00\xcd\xab4\x12\x1f\x00\x00\x00\x0b0\x00\x00'
+                '\x00\x00\xff\xff\xff\xff\xff\xff\xe8\x03\x00\x00\x00\x00\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    def test_set_olt_channel_activation_period_response(self):
+        self.check_parsed(
+            '\x90\xe2\xba\x82\xf9w\x00\x0c\xd5\x00\x01\x01\x00\x1a\x01\x00\x01'
+            '\x00\x10\x00\xcd\xab4\x12\x1f\x00\x00\x00\x0b(\x00\x00\x00\x00'
+            '\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+            PAS5211MsgSetOltChannelActivationPeriodResponse(),
+            channel_id=0
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_send_cli_command
+
+    def test_send_cli_command(self):
+        msg = PAS5211MsgSendCliCommand(command="foo")
+        self.assertEqual(str(msg), '\x03\x00foo')
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSendCliCommand(
+                command="foo\r"
+            ), 11),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x1c\x01\x00'
+                '\x01\x00\x16\x00\xcd\xab4\x12\x0b\x00\x00\x00\x0f0\x00\x00'
+                '\xff\xff\xff\xff\xff\xff\xff\xff\x04\x00\x66\x6f\x6f\x0d\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_switch_to_inbound_mode
+
+    def test_switch_to_inbound_mode(self):
+        msg = PAS5211MsgSendCliCommand(command="foo")
+        self.assertEqual(str(msg), '\x03\x00foo')
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSwitchToInboundMode(
+                mac='00:0c:d5:00:01:00'
+            ), 11, channel_id=0),
+            [
+                '\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00\x1e\x01'
+                '\x00\x01\x00\x18\x00\xcd\xab4\x12\x0b\x00\x00\x00\xec0\x00'
+                '\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x0c\xd5\x00\x01\x00'
+                '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            ]
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_send_frame
+
+    def test_send_frame(self):
+        self.check_gen(
+            constructPAS5211Frames(PAS5211MsgSendFrame(
+                port_type=PON_PORT_PON,
+                port_id=0,
+                management_frame=PON_TRUE,
+                frame=OMCIFrame(
+                    transaction_id=0,
+                    message_type=0x49,
+                    omci_message=OMCIGetRequest(
+                        entity_class=6,
+                        entity_id=0x101,
+                        # there is a more programmer friendly way to express it
+                        attributes_mask=0x0800
+                    )
+                )
+            ), 39, channel_id=0, onu_id=0, onu_session_id=1),
+            [
+                "\x00\x0c\xd5\x00\x01\x00h\x05\xca\x05\xf2\xef\x00J\x01\x00"
+                "\x01\x00D\x00\xcd\xab4\x12'\x00\x00\x00*0\x00\x00\x00\x00"
+                "\x00\x00\x01\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00"
+                "\x00I\n\x00\x06\x01\x01\x08\x00\x00\x00\x00\x00\x00\x00\x00"
+                "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+                "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00("
+            ]
+        )
+
+    def test_send_frame_response(self):
+        self.check_parsed(
+            "\x90\xe2\xba\x82\xf9w\x00\x0c\xd5\x00\x01\x01\x00\x1a\x01\x00"
+            "\x01\x00\x10\x00\xcd\xab4\x12'\x00\x00\x00*(\x00\x00\x00\x00"
+            "\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+            PAS5211MsgSendFrameResponse(
+            ),
+            channel_id=0, onu_id=0, onu_session_id=1
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_receive_onu_activation_event
+
+    def test_receive_onu_activation_event(self):
+        self.check_parsed(
+            '\x90\xe2\xba\x82\xf9w\x00\x0c\xd5\x00\x01\x01\x00&\x01\x00\x01'
+            '\x00\x1c\x00\xcd\xab4\x12\x00\x00\x00\x00\x0c(\x01\x00\x00\x00'
+            '\x00\x00\x01\x00\x00\x00PMCS\xd5b\x84\xac\x04\x12\x04\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+            PAS5211EventOnuActivation(
+                serial_number='PMCS\xd5b\x84\xac',
+                equalization_period=266756
+            ),
+            channel_id=0, onu_id=0, onu_session_id=1
+        )
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~ test_frame_received_event
+
+    '''
+    def test_frame_received_event(self):
+        self.check_parsed(
+            '\x90\xe2\xba\x82\xf9w\x00\x0c\xd5\x00\x01\x01\x00Z\x01\x00\x01'
+            '\x00P\x00\xcd\xab4\x12\x01\x00\x00\x00\x0c(\n\x00\x00\x00\x00'
+            '\x00\x01\x00\x00\x000\x00\x00\x00\x00\x00\x01\x00\x15\x00 \x00'
+            '\x13\x00\x00 \x00\x00)\n\x00\x06\x01\x01\x00\x08\x00PMCS\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+            '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc5\x0c\xb6',
+            PAS5211EventFrameReceived(
+                length=48,
+                management_frame=PON_TRUE,
+                classification_entity=21,  # use enums
+                l3_offset=32,
+                l4_offset=19,
+                # ignored, yet we get a non-zero value from olt
+                ignored=0x2000,
+                frame=OMCIFrame(
+                    transaction_id=0,
+                    message_type=0x29,
+                    omci_message=OMCIGetResponse(
+                        entity_class=6,
+                        entity_id=0x101,
+                        success_code=0,
+                        attributes_mask=0x0800,
+                        data=dict(
+                            vendor_id="PMCS"
+                        )
+                    )
+                )
+            ),
+            channel_id=0, onu_id=0, onu_session_id=1
+        )
+    '''
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/utests/voltha/extensions/omci/test_omci.py b/tests/utests/voltha/extensions/omci/test_omci.py
new file mode 100644
index 0000000..83d79db
--- /dev/null
+++ b/tests/utests/voltha/extensions/omci/test_omci.py
@@ -0,0 +1,46 @@
+from unittest import TestCase, main
+from voltha.extensions.omci.omci import CirtcuitPackEntity, bitpos_from_mask
+from voltha.extensions.omci.omci import EntityClass
+
+
+class TestOmci(TestCase):
+
+    def test_bitpos_from_mask(self):
+
+        f = lambda x: bitpos_from_mask(x)
+        self.assertEqual(f(0), [])
+        self.assertEqual(f(1), [0])
+        self.assertEqual(f(3), [0, 1])
+        self.assertEqual(f(255), [0, 1, 2, 3, 4, 5, 6, 7])
+        self.assertEqual(f(0x800), [11])
+        self.assertEqual(f(0x811), [0, 4, 11])
+
+        f = lambda x: bitpos_from_mask(x, 16, -1)
+        self.assertEqual(f(0), [])
+        self.assertEqual(f(1), [16])
+        self.assertEqual(f(0x800), [5])
+        self.assertEqual(f(0x801), [5, 16])
+
+
+    def test_attribute_indeices_from_mask(self):
+
+        f = EntityClass.attribute_indices_from_mask
+        self.assertEqual(f(0), [])
+        self.assertEqual(f(0x800), [5])
+        self.assertEqual(f(0xf000), [1, 2, 3, 4])
+        self.assertEqual(f(0xf804), [1, 2, 3, 4, 5, 14])
+
+    def test_entity_attribute_serialization(self):
+
+        e = CirtcuitPackEntity(vendor_id='F')
+        self.assertEqual(e.serialize(), 'F\x00\x00\x00')
+
+        e = CirtcuitPackEntity(vendor_id='FOOX')
+        self.assertEqual(e.serialize(), 'FOOX')
+
+        e = CirtcuitPackEntity(vendor_id='FOOX', number_of_ports=16)
+        self.assertEqual(e.serialize(), '\x10FOOX')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/voltha/adapters/__init__.py b/voltha/adapters/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/voltha/adapters/__init__.py
diff --git a/voltha/adapters/microsemi/__init__.py b/voltha/adapters/microsemi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/voltha/adapters/microsemi/__init__.py
diff --git a/voltha/adapters/microsemi/chat.py b/voltha/adapters/microsemi/chat.py
new file mode 100755
index 0000000..484f6c3
--- /dev/null
+++ b/voltha/adapters/microsemi/chat.py
@@ -0,0 +1,684 @@
+#!/usr/bin/env python
+
+from hexdump import hexdump
+from threading import Thread
+from time import sleep
+
+from scapy.config import conf
+from scapy.fields import Field, lhex, MACField, LenField, LEShortField, \
+    LEIntField, LESignedIntField, FieldLenField, FieldListField, PacketField, \
+    ByteField, StrField, ConditionalField, StrFixedLenField
+from scapy.layers.l2 import DestMACField, ETHER_ANY, ETH_P_ALL, sniff, sendp
+from scapy.layers.ntp import XLEShortField
+from scapy.packet import Packet, bind_layers
+from scapy.volatile import RandSInt
+
+from voltha.extensions.omci.omci import OMCIFrame
+
+
+src_mac = "68:05:ca:05:f2:ef"
+dst_mac = "00:0c:d5:00:01:00"
+
+
+# from enum PON_true_false_t
+PON_FALSE = 0
+PON_TRUE = 1
+
+# from enum PON_enable_disable_t
+PON_DISABLE = 0
+PON_ENABLE = 1
+
+# from enym PON_mac_t
+PON_MII = 0
+PON_GMII = 1
+PON_TBI = 2
+
+PON_POLARITY_ACTIVE_LOW = 0
+PON_POLARITY_ACTIVE_HIGH = 1
+
+PON_OPTICS_VOLTAGE_IF_UNDEFINED = 0
+PON_OPTICS_VOLTAGE_IF_CML = 1
+PON_OPTICS_VOLTAGE_IF_LVPECL = 2
+
+PON_SD_SOURCE_LASER_SD = 0
+PON_SD_SOURCE_BCDR_LOCK = 1
+PON_SD_SOURCE_BCDR_SD = 2
+
+PON_RESET_TYPE_DELAY_BASED = 0
+PON_RESET_TYPE_SINGLE_RESET = 1
+PON_RESET_TYPE_DOUBLE_RESET = 2
+
+PON_RESET_TYPE_NORMAL_START_BURST_BASED = 0
+PON_RESET_TYPE_NORMAL_END_BURST_BASED = 1
+
+PON_GPIO_LINE_0 = 0
+PON_GPIO_LINE_1 = 1
+PON_GPIO_LINE_2 = 2
+PON_GPIO_LINE_3 = 3
+PON_GPIO_LINE_4 = 4
+PON_GPIO_LINE_5 = 5
+PON_GPIO_LINE_6 = 6
+PON_GPIO_LINE_7 = 7
+def PON_EXT_GPIO_LINE(line):
+    return line + 8
+
+# from enum PON_alarm_t
+PON_ALARM_SOFTWARE_ERROR = 0
+PON_ALARM_LOS = 1
+PON_ALARM_LOSI = 2
+PON_ALARM_DOWI = 3
+PON_ALARM_LOFI = 4
+PON_ALARM_RDII = 5
+PON_ALARM_LOAMI = 6
+PON_ALARM_LCDGI = 7
+PON_ALARM_LOAI = 8
+PON_ALARM_SDI = 9
+PON_ALARM_SFI = 10
+PON_ALARM_PEE = 11
+PON_ALARM_DGI = 12
+PON_ALARM_LOKI = 13
+PON_ALARM_TIWI = 14
+PON_ALARM_TIA = 15
+PON_ALARM_VIRTUAL_SCOPE_ONU_LASER_ALWAYS_ON = 16
+PON_ALARM_VIRTUAL_SCOPE_ONU_SIGNAL_DEGRADATION = 17
+PON_ALARM_VIRTUAL_SCOPE_ONU_EOL = 18
+PON_ALARM_VIRTUAL_SCOPE_ONU_EOL_DATABASE_IS_FULL = 19
+PON_ALARM_AUTH_FAILED_IN_REGISTRATION_ID_MODE = 20
+PON_ALARM_SUFI = 21
+PON_ALARM_LAST_ALARM = 22
+
+# from enum PON_general_parameters_type_t
+PON_COMBINED_LOSI_LOFI 		            = 1000
+PON_TX_ENABLE_DEFAULT 		            = 1001
+
+# Enable or disable False queue full event from DBA
+PON_FALSE_Q_FULL_EVENT_MODE             = 1002
+
+# Set PID_AID_MISMATCH min silence period. 0 - disable, Else - period in secs
+PON_PID_AID_MISMATCH_MIN_SILENCE_PERIOD = 1003
+
+# Set if FW generate clear alarm. 0 - generate clear alarm, Else - don't
+# generate clear alarm
+PON_ENABLE_CLEAR_ALARM                  = 1004
+
+# Enable or disabl send assign alloc id ploam. 0 - disable, 1 - enable
+PON_ASSIGN_ALLOC_ID_PLOAM               = 1005
+
+# BIP error polling period, 200 - 65000, 0 - Disabled, Recommended: 5000
+# (default)
+PON_BIP_ERR_POLLING_PERIOD_MS           = 1006
+
+# Ignore SN when decatived 0 - consider SN (deactivate the onu if received
+# same SN when activated (default)	1 - Ignore
+PON_IGNORE_SN_WHEN_ACTIVE		        = 1007
+
+# 0xffffffff - Disabled (default). Any other value (0 - 0xfffe) indicates
+# that PA delay is enabled, with the specified delay value and included in
+# the US_OVERHEAD PLOAM
+PON_ONU_PRE_ASSIGNED_DELAY		        = 1008
+
+# Enable or disable DS fragmentation, 0 disable, 1 enable
+PON_DS_FRAGMENTATION			        = 1009
+
+# Set if fw report rei alarm when errors is 0, 0 disable (default), 1 enable
+PON_REI_ERRORS_REPORT_ALL		        = 1010
+
+# Set if igonre sfi deactivation, 0 disable (default), 1 enable
+PON_IGNORE_SFI_DEACTIVATION		        = 1011
+
+# Allows to override the allocation overhead set by optic-params
+# configuration. This configuration is only allowed when the the pon channel
+# is disabled
+PON_OVERRIDE_ALLOCATION_OVERHEAD	    = 1012
+
+# Optics timeline offset, -128-127, : this parameter is very sensitive and
+# requires coordination with PMC
+PON_OPTICS_TIMELINE_OFFSET	            = 1013
+
+# Last general meter
+PON_LAST_GENERAL_PARAMETER		        = PON_OPTICS_TIMELINE_OFFSET
+
+# from enum PON_dba_mode_t
+PON_DBA_MODE_NOT_LOADED                 = 0
+PON_DBA_MODE_LOADED_NOT_RUNNING         = 1
+PON_DBA_MODE_RUNNING                    = 2
+PON_DBA_MODE_LAST                       = 3
+
+# from enum type typedef enum PON_port_frame_destination_t
+PON_PORT_PON = 0
+PON_PORT_SYSTEM = 1
+
+# from enum PON_olt_hw_classification_t
+
+PON_OLT_HW_CLASSIFICATION_PAUSE                    = 0
+PON_OLT_HW_CLASSIFICATION_LINK_CONSTRAINT          = 1
+PON_OLT_HW_CLASSIFICATION_IGMP                     = 2
+PON_OLT_HW_CLASSIFICATION_MPCP                     = 3
+PON_OLT_HW_CLASSIFICATION_OAM                      = 4
+PON_OLT_HW_CLASSIFICATION_802_1X                   = 5
+PON_OLT_HW_CLASSIFICATION_PPPOE_DISCOVERY          = 6
+PON_OLT_HW_CLASSIFICATION_PPPOE_SESSION            = 7
+PON_OLT_HW_CLASSIFICATION_DHCP_V4                  = 8
+PON_OLT_HW_CLASSIFICATION_PIM                      = 9
+PON_OLT_HW_CLASSIFICATION_DHCP_V6                  = 10
+PON_OLT_HW_CLASSIFICATION_ICMP_V4                  = 11
+PON_OLT_HW_CLASSIFICATION_MLD                      = 12
+PON_OLT_HW_CLASSIFICATION_ARP                      = 13
+PON_OLT_HW_CLASSIFICATION_CONF_DA                  = 14
+PON_OLT_HW_CLASSIFICATION_CONF_RULE                = 15
+PON_OLT_HW_CLASSIFICATION_DA_EQ_SA                 = 16
+PON_OLT_HW_CLASSIFICATION_DA_EQ_MAC                = 17
+PON_OLT_HW_CLASSIFICATION_DA_EQ_SEC_MAC            = 18
+PON_OLT_HW_CLASSIFICATION_SA_EQ_MAC                = 19
+PON_OLT_HW_CLASSIFICATION_SA_EQ_SEC_MAC            = 20
+PON_OLT_HW_CLASSIFICATION_ETHERNET_MANAGEMENT      = 100
+PON_OLT_HW_CLASSIFICATION_IPV4_LOCAL_MULTICAST     = 101
+PON_OLT_HW_CLASSIFICATION_IPV4_MANAGEMENT          = 102
+PON_OLT_HW_CLASSIFICATION_ALL_IPV4_MULTICAST       = 103
+PON_OLT_HW_CLASSIFICATION_IPV6_LOCAL_MULTICAST     = 104
+PON_OLT_HW_CLASSIFICATION_IPV6_MANAGEMENT          = 105
+PON_OLT_HW_CLASSIFICATION_ALL_IPV6_MULTICAST       = 106
+PON_OLT_HW_CLASSIFICATION_OTHER			           = 107
+PON_OLT_HW_CLASSIFICATION_LAST_RULE                = 108
+
+
+class XLESignedIntField(Field):
+    def __init__(self, name, default):
+        Field.__init__(self, name, default, "<i")
+    def randval(self):
+        return RandSInt()
+    def i2repr(self, pkt, x):
+        return lhex(self.i2h(pkt, x))
+
+
+class LESignedShortField(Field):
+    def __init__(self, name, default):
+        Field.__init__(self, name, default, "<h")
+
+
+class PAS5211Dot3(Packet):
+    name = "PAS5211Dot3"
+    fields_desc = [ DestMACField("dst"),
+                    MACField("src", ETHER_ANY),
+                    LenField("len", None, "H") ]
+
+    MIN_FRAME_SIZE = 60
+
+    def post_build(self, pkt, payload):
+        pkt += payload
+        size = ord(payload[4]) + (ord(payload[5]) << 8)
+        length = size + 6  # this is a idiosyncracy of the PASCOMM protocol
+        pkt = pkt[:12] + chr(length >> 8) + chr(length & 0xff) + pkt[14:]
+        padding = self.MIN_FRAME_SIZE - len(pkt)
+        if padding > 0:
+            pkt = pkt + ("\x00" * padding)
+        return pkt
+
+
+class PAS5211FrameHeader(Packet):
+    name = "PAS5211FrameHeader"
+    fields_desc = [
+        LEShortField("part", 1),
+        LEShortField("total_parts", 1),
+        LEShortField("size", 0),
+        XLESignedIntField("magic_number", 0x1234ABCD)
+    ]
+
+
+class PAS5211MsgHeader(Packet):
+    name = "PAS5211MsgHeader"
+    fields_desc = [
+        LEIntField("sequence_number", 0),
+        XLEShortField("opcode", 0),
+        LEShortField("event_type", 0),
+        LESignedShortField("channel_id", -1),
+        LESignedShortField("onu_id", -1),
+        LESignedIntField("onu_session_id", -1)
+    ]
+
+
+class PAS5211Msg(Packet):
+    opcode = "Must be filled by subclass"
+    pass
+
+
+class PAS5211MsgGetProtocolVersion(PAS5211Msg):
+    opcode = 2
+    name = "PAS5211MsgGetProtocolVersion"
+    fields_desc = [ ]
+
+
+class PAS5211MsgGetProtocolVersionResponse(PAS5211Msg):
+    name = "PAS5211MsgGetProtocolVersionResponse"
+    fields_desc = [
+        LEShortField("major_hardware_version", 0),
+        LEShortField("minor_hardware_version", 0),
+        LEShortField("major_pfi_version", 0),
+        LEShortField("minor_pfi_version", 0)
+    ]
+
+
+class PAS5211MsgGetOltVersion(PAS5211Msg):
+    opcode = 3
+    name = "PAS5211MsgGetOltVersion"
+    fields_desc = [ ]
+
+
+class PAS5211MsgGetOltVersionResponse(PAS5211Msg):
+    name = "PAS5211MsgGetOltVersionResponse"
+    fields_desc = [
+        LEShortField("major_firmware_version", 0),
+        LEShortField("minor_firmware_version", 0),
+        LEShortField("build_firmware_version", 0),
+        LEShortField("maintenance_firmware_version", 0),
+        LEShortField("major_hardware_version", 0),
+        LEShortField("minor_hardware_version", 0),
+        LEIntField("system_port_mac_type", 0),
+        FieldLenField("channels_supported", 0, fmt="<H"),
+        LEShortField("onus_supported_per_channel", 0),
+        LEShortField("ports_supported_per_channel", 0),
+        LEShortField("alloc_ids_supported_per_channel", 0),
+        FieldListField("critical_events_counter", [0, 0, 0, 0],
+                       LEIntField("entry", 0),
+                       count_from=lambda pkt: pkt.channels_supported),
+        FieldListField("non_critical_events_counter", [0, 0, 0, 0],
+                       LEIntField("entry", 0),
+                       count_from=lambda pkt: pkt.channels_supported)
+    ]
+
+
+class SnrBurstDelay(Packet):
+    name = "SnrBurstDelay"
+    fields_desc= [
+        LEShortField("timer_delay", None),
+        LEShortField("preamble_delay", None),
+        LEShortField("delimiter_delay", None),
+        LEShortField("burst_delay", None)
+    ]
+
+
+class RngBurstDelay(Packet):
+    name = "SnrBurstDelay"
+    fields_desc= [
+        LEShortField("timer_delay", None),
+        LEShortField("preamble_delay", None),
+        LEShortField("delimiter_delay", None)
+    ]
+
+
+class BurstTimingCtrl(Packet):
+    name = "BurstTimingCtrl"
+    fields_desc = [
+        PacketField("snr_burst_delay", None, SnrBurstDelay),
+        PacketField("rng_burst_delay", None, RngBurstDelay),
+        LEShortField("burst_delay_single", None),
+        LEShortField("burst_delay_double", None)
+
+    ]
+
+
+class GeneralOpticsParams(Packet):
+    name = "GeneralOpticsParams"
+    fields_desc= [
+        ByteField("laser_reset_polarity", None),
+        ByteField("laser_sd_polarity", None),
+        ByteField("sd_source", None),
+        ByteField("sd_hold_snr_ranging", None),
+        ByteField("sd_hold_normal", None),
+        ByteField("reset_type_snr_ranging", None),
+        ByteField("reset_type_normal", None),
+        ByteField("laser_reset_enable", None),
+    ]
+
+
+class ResetValues(Packet):
+    name = "ResetDataBurst"
+    fields_desc = [
+        ByteField("bcdr_reset_d2", None),
+        ByteField("bcdr_reset_d1", None),
+        ByteField("laser_reset_d2", None),
+        ByteField("laser_reset_d1", None)
+    ]
+
+
+class DoubleResetValues(Packet):
+    name = "ResetDataBurst"
+    fields_desc = [
+        ByteField("bcdr_reset_d4", None),
+        ByteField("bcdr_reset_d3", None),
+        ByteField("laser_reset_d4", None),
+        ByteField("laser_reset_d3", None)
+    ]
+
+
+class ResetTimingCtrl(Packet):
+    name = "ResetTimingCtrl"
+    fields_desc = [
+        PacketField("reset_data_burst", None, ResetValues),
+        PacketField("reset_snr_burst", None, ResetValues),
+        PacketField("reset_rng_burst", None, ResetValues),
+        PacketField("single_reset", None, ResetValues),
+        PacketField("double_reset", None, DoubleResetValues),
+    ]
+
+
+class PreambleParams(Packet):
+    name = "PreambleParams"
+    fields_desc = [
+        ByteField("correlation_preamble_length", None),
+        ByteField("preamble_length_snr_rng", None),
+        ByteField("guard_time_data_mode", None),
+        ByteField("type1_size_data", None),
+        ByteField("type2_size_data", None),
+        ByteField("type3_size_data", None),
+        ByteField("type3_pattern", None),
+        ByteField("delimiter_size", None),
+        ByteField("delimiter_byte1", None),
+        ByteField("delimiter_byte2", None),
+        ByteField("delimiter_byte3", None)
+    ]
+
+
+class PAS5211MsgSetOltOptics(PAS5211Msg):
+    opcode = 106
+    name = "PAS5211MsgSetOltOptics"
+    fields_desc = [
+        PacketField("burst_timing_ctrl", None, BurstTimingCtrl),
+        PacketField("general_optics_params", None, GeneralOpticsParams),
+        ByteField("reserved1", 0),
+        ByteField("reserved2", 0),
+        ByteField("reserved3", 0),
+        PacketField("reset_timing_ctrl", None, ResetTimingCtrl),
+        ByteField("voltage_if_mode", None),
+        PacketField("preamble_params", None, PreambleParams),
+        ByteField("reserved4", 0),
+        ByteField("reserved5", 0),
+        ByteField("reserved6", 0)
+    ]
+
+
+class PAS5211MsgSetOltOpticsResponse(PAS5211Msg):
+    name = "PAS5211MsgSetOltOpticsResponse"
+    fields_desc = [ ]
+
+
+class PAS5211MsgSetOpticsIoControl(PAS5211Msg):
+    opcode = 108
+    name = "PAS5211MsgSetOpticsIoControl"
+    fields_desc = [
+        ByteField("i2c_clk", None),
+        ByteField("i2c_data", None),
+        ByteField("tx_enable", None),
+        ByteField("tx_fault", None),
+        ByteField("tx_enable_polarity", None),
+        ByteField("tx_fault_polarity", None),
+    ]
+
+
+class PAS5211MsgSetOpticsIoControlResponse(PAS5211Msg):
+    name = "PAS5211MsgSetOpticsIoControlResponse"
+    fields_desc = [ ]
+
+
+class PAS5211MsgSetGeneralParam(PAS5211Msg):
+    opcode = 164
+    name = "PAS5211MsgSetGeneralParam"
+    fields_desc = [
+        LEIntField("parameter", None),
+        LEIntField("reserved", 0),
+        LEIntField("value", None)
+    ]
+
+
+class PAS5211MsgSetGeneralParamResponse(PAS5211Msg):
+    name = "PAS5211MsgSetGeneralParamResponse"
+    fields_desc = []
+
+
+class PAS5211MsgGetGeneralParam(PAS5211Msg):
+    opcode = 165
+    name = "PAS5211MsgGetGeneralParam"
+    fields_desc = [
+        LEIntField("parameter", None),
+        LEIntField("reserved", 0),
+    ]
+
+
+class PAS5211MsgGetGeneralParamResponse(PAS5211Msg):
+    name = "PAS5211MsgGetGeneralParamResponse"
+    fields_desc = [
+        LEIntField("parameter", None),
+        LEIntField("reserved", 0),
+        LEIntField("value", None)
+    ]
+
+
+class PAS5211MsgGetDbaMode(PAS5211Msg):
+    opcode = 57
+    name = "PAS5211MsgGetDbaMode"
+    fields_desc = []
+
+
+class PAS5211MsgGetDbaModeResponse(PAS5211Msg):
+    name = "PAS5211MsgGetDbaModeResponse"
+    fields_desc = [
+        LEIntField("dba_mode", None),
+    ]
+
+
+class PAS5211MsgAddOltChannel(PAS5211Msg):
+    opcode = 4
+    name = "PAS5211MsgAddOltChannel"
+    fields_desc = [
+
+    ]
+
+
+class PAS5211MsgAddOltChannelResponse(PAS5211Msg):
+    name = "PAS5211MsgAddOltChannelResponse"
+    fields_desc = [
+
+    ]
+
+
+class PAS5211MsgSetAlarmConfig(PAS5211Msg):
+    opcode = 48
+    name = "PAS5211MsgSetAlarmConfig"
+    fields_desc = [
+        LEShortField("type", None),
+        LEShortField("activate", None),
+        LEIntField("parameter1", None),
+        LEIntField("parameter2", None),
+        LEIntField("parameter3", None),
+        LEIntField("parameter4", None)
+    ]
+
+
+class PAS5211MsgSetOltChannelActivationPeriod(PAS5211Msg):
+    opcode = 11
+    name = "PAS5211MsgSetOltChannelActivationPeriod"
+    fields_desc = [
+        LEIntField("activation_period", None)
+    ]
+
+
+class PAS5211MsgSetOltChannelActivationPeriodResponse(PAS5211Msg):
+    name = "PAS5211MsgSetOltChannelActivationPeriodResponse"
+    fields_desc = []
+
+
+class PAS5211MsgSetAlarmConfigResponse(PAS5211Msg):
+    name = "PAS5211MsgSetAlarmConfigResponse"
+    fields_desc = []
+
+
+class PAS5211MsgSendCliCommand(PAS5211Msg):
+    opcode = 15
+    name = "PAS5211MsgSendCliCommand"
+    fields_desc = [
+        FieldLenField("size", None, fmt="<H", length_of="command"),
+        StrField("command", "")
+    ]
+
+
+class PAS5211MsgSwitchToInboundMode(PAS5211Msg):
+    opcode = 0xec
+    name = "PAS5211MsgSwitchToInboundMode"
+    fields_desc = [
+        MACField("mac", None),
+        LEShortField("mode", 0)
+    ]
+
+
+class Frame(Packet):
+    pass
+
+
+class PAS5211MsgSendFrame(PAS5211Msg):
+    opcode = 42
+    name = "PAS5211MsgSendFrame"
+    fields_desc = [
+        FieldLenField("length", None, fmt="<H", length_of="frame"),
+        LEShortField("port_type", PON_PORT_PON),
+        LEShortField("port_id", 0),
+        LEShortField("management_frame", PON_FALSE),
+        PacketField("frame", None, Packet)
+    ]
+
+
+class PAS5211MsgSendFrameResponse(PAS5211Msg):
+    name = "PAS5211MsgSendFrameResponse"
+    fields_desc = []
+
+
+class PAS5211Event(PAS5211Msg):
+    opcode = 12
+
+
+class PAS5211EventFrameReceived(PAS5211Event):
+    name = "PAS5211EventFrameReceived"
+    fields_desc = [
+        FieldLenField("length", None, length_of="frame", fmt="<H"),
+        LEShortField("port_type", PON_PORT_PON),
+        LEShortField("port_id", 0),
+        LEShortField("management_frame", PON_FALSE),
+        LEShortField("classification_entity", None),
+        LEShortField("l3_offset", None),
+        LEShortField("l4_offset", None),
+        LEShortField("ignored", 0), # TODO these do receive values, but there is no code in PMC using it
+        ConditionalField(PacketField("frame", None, Packet), lambda pkt: pkt.management_frame==PON_FALSE),
+        ConditionalField(PacketField("frame", None, OMCIFrame), lambda pkt: pkt.management_frame==PON_TRUE)
+    ]
+
+
+class PAS5211EventOnuActivation(PAS5211Event):
+    name = "PAS5211EventOnuActivation"
+    fields_desc = [
+        StrFixedLenField("serial_number", None, length=8),
+        LEIntField("equalization_period", None)
+    ]
+
+
+# bindings for messages received
+bind_layers(PAS5211Dot3, PAS5211FrameHeader)
+bind_layers(PAS5211FrameHeader, PAS5211MsgHeader)
+bind_layers(PAS5211MsgHeader, PAS5211MsgGetProtocolVersionResponse, opcode=0x2800 | 2)
+bind_layers(PAS5211MsgHeader, PAS5211MsgGetOltVersionResponse, opcode=0x3800 | 3)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSetOltOpticsResponse, opcode=0x2800 | 106)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSetOpticsIoControlResponse, opcode=0x2800 | 108)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSetGeneralParamResponse, opcode=0x2800 | 164)
+bind_layers(PAS5211MsgHeader, PAS5211MsgGetGeneralParamResponse, opcode=0x2800 | 165)
+bind_layers(PAS5211MsgHeader, PAS5211MsgAddOltChannelResponse, opcode=0x2800 | 4)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSetAlarmConfigResponse, opcode=0x2800 | 48)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSetOltChannelActivationPeriodResponse, opcode=0x2800 | 11)
+bind_layers(PAS5211MsgHeader, PAS5211MsgGetDbaModeResponse, opcode=0x2800 | 57)
+bind_layers(PAS5211MsgHeader, PAS5211MsgSendFrameResponse, opcode=0x2800 | 42)
+
+# bindings for events received
+bind_layers(PAS5211MsgHeader, PAS5211EventOnuActivation, opcode=0x2800 | 12, event_type=1)
+bind_layers(PAS5211MsgHeader, PAS5211EventFrameReceived, opcode=0x2800 | 12, event_type=10)
+bind_layers(PAS5211MsgHeader, PAS5211Event, opcode=0x2800 | 12)
+
+
+def constructPAS5211Frames(msg, seq, channel_id=-1, onu_id=-1, onu_session_id=-1):
+
+    assert isinstance(msg, PAS5211Msg)
+    opcode = 0x3000 | msg.opcode
+
+    inner_msg = PAS5211MsgHeader(
+        sequence_number=seq,
+        opcode=opcode,
+        channel_id=channel_id,
+        onu_id=onu_id,
+        onu_session_id=onu_session_id
+    ) / msg
+    size = len(inner_msg)
+
+    frame_body = PAS5211FrameHeader(size=size) / inner_msg
+
+    frame = PAS5211Dot3(src=src_mac, dst=dst_mac) / frame_body
+
+    return [frame]
+
+
+class Receiver(Thread):
+
+    def __init__(self, iface):
+        Thread.__init__(self)
+        self.iface = iface
+        self.finished = False
+
+    def run(self):
+        self.sock = s = conf.L2listen( type=ETH_P_ALL, iface=self.iface, filter='inbound')
+        while not self.finished:
+            try:
+                sniffed = sniff(1, iface=self.iface, timeout=1, opened_socket=s)
+                for frame in sniffed:
+                    self.process_frame(frame)
+            except Exception, e:
+                print("ERROR: scanpy.sniff error:", e)
+
+    def stop(self):
+        assert not self.finished
+        self.finished = True
+        self.sock.close()
+        self.join()
+
+    def process_frame(self, frame):
+        print "================== Received frame: ================="
+        print "Hexdump:"
+        hexdump(str(frame))
+        print "Raw string:"
+        print(repr(str(frame)))
+        print "Reconstructed frame:"
+        frame.show()
+
+
+if __name__ == '__main__':
+
+    seq = 1
+    def get_seq():
+        global seq
+        seq += 1
+        return seq
+
+    iface = "enp3s0"
+
+    receiver = Receiver(iface)
+    receiver.start()
+    sleep(0.1) # to allow the listening socket be opened
+
+    try:
+        sendp(constructPAS5211Frames(PAS5211MsgGetProtocolVersion(), get_seq())[0], iface=iface)
+        sleep(0.1)
+        sendp(constructPAS5211Frames(PAS5211MsgGetOltVersion(), get_seq())[0], iface=iface)
+        sleep(0.1)
+        sendp(constructPAS5211Frames(PAS5211MsgSendCliCommand(command="\r"), get_seq())[0], iface=iface)
+        sleep(5)
+
+    except Exception, e:
+        raise e
+
+    finally:
+        receiver.stop()
+
diff --git a/voltha/adapters/microsemi/send.py b/voltha/adapters/microsemi/send.py
new file mode 100644
index 0000000..8697ae7
--- /dev/null
+++ b/voltha/adapters/microsemi/send.py
@@ -0,0 +1,85 @@
+from scapy.all import *
+
+MIN_FRAME_SIZE = 60
+
+src_mac = "68:05:ca:05:f2:ef"
+dst_mac = "00:0c:d5:00:01:00"
+
+
+class PAS5211Dot3(Dot3):
+    name = "PAS5211Dot3"
+
+    def post_build(self, pkt, payload):
+        pkt += payload
+        size = ord(payload[4]) + (ord(payload[5]) << 8)
+        length = size + 6  # this is a idiosyncracy of the PASCOMM protocol
+        pkt = pkt[:12] + chr(length >> 8) + chr(length & 0xff) + pkt[14:]
+        padding = MIN_FRAME_SIZE - len(pkt)
+        if padding > 0:
+            pkt = pkt + ("\x00" * padding)
+        return pkt
+    
+    
+class PAS5211FrameHeader(Packet):
+    name = "PAS5211FrameHeader"
+    fields_desc = [ LEShortField("part", 1),
+                    LEShortField("total_parts", 1),
+                    LEShortField("size", 0),
+                    LEIntField("magic_number", 0x1234ABCD) ]
+
+        
+conf.neighbor.register_l3(Dot3, PAS5211FrameHeader, lambda l2,l3: conf.neighbor.resolve(l2,l3.payload))
+
+
+class PAS5211MsgHeader(Packet):
+    name = "PAS5211MsgHeader"
+    fields_desc = [ LEIntField("sequence_number", 0),
+                    LEShortField("opcode", 0) ]
+    
+
+class PAS5211MsgEntityHeader(Packet): # PASCOMM_GPON_msg_entity_hdr
+    name = "PAS5211MsgEntityHeader"
+    fields_desc = [ LEShortField("reserved", 0),
+                    LEShortField("channel_id", 0xffff),
+                    LEShortField("onu_id", 0xffff),
+                    LESignedIntField("onu_session_id", -1) ]
+
+
+class PAS5211Msg(Packet):
+    opcode = "Must be filled by subclass"
+    pass
+
+
+class PAS5211MsgGetProtocolVersion(PAS5211Msg):
+    opcode = 2
+    name = "PAS5211MsgGetProtocolVersion"
+    fields_desc = [ ]
+
+
+class PAS5211MsgGetOltVersion(PAS5211Msg):
+    opcode = 3
+    name = "PAS5211MsgGetOltVersion"
+    fields_desc = [ ]
+
+    
+def constructPAS5211Frames(msg, seq):
+
+    assert isinstance(msg, PAS5211Msg)
+    opcode = 0x3000 | msg.opcode
+
+    entity_hdr = PAS5211MsgEntityHeader() # we may need non-def values later
+
+    inner_msg = PAS5211MsgHeader(sequence_number=seq, opcode=opcode) \
+        / msg \
+        / entity_hdr
+    size = len(inner_msg)
+    hexdump(inner_msg)
+    
+    frame_body = PAS5211FrameHeader(size=size) / inner_msg
+    
+    frame = PAS5211Dot3(src=src_mac, dst=dst_mac) / frame_body
+
+    return frame
+
+frame = constructPAS5211Frames(PAS5211MsgGetProtocolVersion(), 1) [0]
+hexdump(frame)
diff --git a/voltha/extensions/__init__.py b/voltha/extensions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/voltha/extensions/__init__.py
diff --git a/voltha/extensions/omci/__init__.py b/voltha/extensions/omci/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/voltha/extensions/omci/__init__.py
diff --git a/voltha/extensions/omci/omci.py b/voltha/extensions/omci/omci.py
new file mode 100644
index 0000000..aa14b92
--- /dev/null
+++ b/voltha/extensions/omci/omci.py
@@ -0,0 +1,217 @@
+import inspect
+import sys
+from enum import Enum
+# from scapy.all import StrFixedLenField, ByteField, ShortField, ConditionalField, \
+#     PacketField, PadField, IntField, Field, Packet
+from scapy.fields import ByteField, Field, ShortField, PacketField, PadField, \
+    ConditionalField
+from scapy.fields import StrFixedLenField, IntField
+from scapy.packet import Packet
+
+
+def bitpos_from_mask(mask, lsb_pos=0, increment=1):
+    """
+    Turn a decimal value (bitmask) into a list of indices where each
+    index value corresponds to the bit position of a bit that was set (1)
+    in the mask. What numbers are assigned to the bit positions is controlled
+    by lsb_pos and increment, as explained below.
+    :param mask: a decimal value used as a bit mask
+    :param lsb_pos: The decimal value associated with the LSB bit
+    :param increment: If this is +i, then the bit next to LSB will take
+    the decimal value of lsb_pos + i.
+    :return: List of bit positions where the bit was set in mask
+    """
+    out = []
+    while mask:
+        if mask & 0x01:
+            out.append(lsb_pos)
+        lsb_pos += increment
+        mask >>= 1
+    return sorted(out)
+
+
+class AttributeAccess(Enum):
+    Readable = 1
+    R = 1
+    Writable = 2
+    W = 2
+    SetByCreate = 3
+    SBC = 3
+
+
+class EntityOperations(Enum):
+    Get = 1  # TODO adjust encoding to match msg_type field
+    Set = 2
+    Create = 3
+    Delete = 4
+    Reboot = 10
+    Test = 11
+
+
+class EntityClassAttribute:
+
+    def __init__(self, fld, access=set(), optional=False):
+        self._fld = fld
+        self._access = access
+        self._optional = optional
+
+class EntityClass:
+    class_id = 'to be filled by subclass'
+    attributes = []
+    mandatory_operations = {}
+    optional_operations = {}
+
+    # will be map of attr_name -> index in attributes
+    attribute_name_to_index_map = None
+
+    def __init__(self, **kw):
+
+        assert(isinstance(kw, dict))
+
+        # verify that all keys provided are valid in the entity
+        if self.attribute_name_to_index_map is None:
+            self.__class__.attribute_name_to_index_map = dict(
+                (a._fld.name, idx) for idx, a in enumerate(self.attributes))
+
+        for k, v in kw.iteritems():
+            assert(k in self.attribute_name_to_index_map)
+
+        self._data = kw
+
+    def serialize(self, mask=None, operation=None):
+        bytes = ''
+
+        # generate ordered list of attribute indices needed to be processed
+        # if mask is provided, we use that explicitly
+        # if mask is not provided, we determine attributes from the self._data content
+        # also taking into account the type of operation in hand
+        if mask is not None:
+            attribute_indices = EntityClass.attribute_indices_from_mask(mask)
+            print attribute_indices
+        else:
+            attribute_indices = self.attribute_indices_from_data()
+
+        # Serialize each indexed field (ignoring entity id)
+        for index in attribute_indices:
+            field = self.attributes[index]._fld
+            bytes = field.addfield(None, bytes, self._data[field.name])
+
+        return bytes
+
+    def attribute_indices_from_data(self):
+        return sorted(
+            self.attribute_name_to_index_map[attr_name]
+            for attr_name in self._data.iterkeys())
+
+    byte1_mask_to_attr_indices = dict(
+        (m, bitpos_from_mask(m, 8, -1)) for m in range(256))
+    byte2_mask_to_attr_indices = dict(
+        (m, bitpos_from_mask(m, 16, -1)) for m in range(256))
+    @classmethod
+    def attribute_indices_from_mask(cls, mask):
+        # each bit in the 2-byte field denote an attribute index; we use a
+        # lookup table to make lookup a bit faster
+        return \
+            cls.byte1_mask_to_attr_indices[(mask >> 8) & 0xff] + \
+            cls.byte2_mask_to_attr_indices[(mask & 0xff)]
+
+
+# abbreviations
+ECA = EntityClassAttribute
+AA = AttributeAccess
+OP = EntityOperations
+
+
+class CirtcuitPackEntity(EntityClass):
+    class_id = 6
+    attributes = [
+        ECA(StrFixedLenField("managed_entity_id", None, 22), {AA.R, AA.SBC}),
+        ECA(ByteField("type", None), {AA.R, AA.SBC}),
+        ECA(ByteField("number_of_ports", None), {AA.R}, optional=True),
+        ECA(StrFixedLenField("serial_number", None, 8), {AA.R}),
+        ECA(StrFixedLenField("version", None, 14), {AA.R}),
+        ECA(StrFixedLenField("vendor_id", None, 4), {AA.R}),
+        ECA(ByteField("administrative_state", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("operational_state", None), {AA.R}, optional=True),
+        ECA(ByteField("bridged_or_ip_ind", None), {AA.R, AA.W}, optional=True),
+        ECA(StrFixedLenField("equipment_id", None, 20), {AA.R}, optional=True),
+        ECA(ByteField("card_configuration", None), {AA.R, AA.W, AA.SBC}), # not really mandatory, see spec
+        ECA(ByteField("total_tcont_buffer_number", None), {AA.R}),
+        ECA(ByteField("total_priority_queue_number", None), {AA.R}),
+        ECA(ByteField("total_traffic_scheduler_number", None), {AA.R}),
+        ECA(IntField("power_sched_override", None), {AA.R, AA.W}, optional=True)
+    ]
+    mandatory_operations = {OP.Get, OP.Set, OP.Reboot}
+    optional_operations = {OP.Create, OP.Delete, OP.Test}
+
+
+# entity class lookup table from entity_class values
+entity_classes_name_map = dict(
+    inspect.getmembers(sys.modules[__name__],
+    lambda o: inspect.isclass(o) and \
+              issubclass(o, EntityClass) and \
+              o is not EntityClass)
+)
+
+entity_classes = [c for c in entity_classes_name_map.itervalues()]
+entity_id_to_class_map = dict((c.class_id, c) for c in entity_classes)
+
+
+class OMCIData(Field):
+
+    __slots__ = Field.__slots__ + ['_entity_class', '_attributes_mask']
+
+    def __init__(self, name, entity_class="entity_class",
+                 attributes_mask="attributes_mask"):
+        Field.__init__(self, name=name, default=None, fmt='s')
+        self._entity_class = entity_class
+        self._attributes_mask = attributes_mask
+
+    def i2m(self, pkt, x):
+        class_id = getattr(pkt, self._entity_class)
+        attribute_mask = getattr(pkt, self._attributes_mask)
+        entity_class = entity_id_to_class_map.get(class_id)
+        return entity_class(**x).serialize(attribute_mask)
+
+
+class OMCIMessage(Packet):
+    name = "OMCIMessage"
+    fields_desc = []
+
+
+class OMCIGetRequest(OMCIMessage):
+    name = "OMCIGetRequest"
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ShortField("attributes_mask", None)
+    ]
+
+
+class OMCIGetResponse(OMCIMessage):
+    name = "OMCIGetResponse"
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ByteField("success_code", 0),
+        ShortField("attributes_mask", None),
+        OMCIData("data", entity_class="entity_class",
+                 attributes_mask="attributes_mask")
+    ]
+
+
+class OMCIFrame(Packet):
+    name = "OMCIFrame"
+    fields_desc = [
+        ShortField("transaction_id", 0),
+        ByteField("message_type", None),
+        ByteField("omci", 0x0a),
+        ConditionalField(PadField(PacketField("omci_message", None,
+                                              OMCIGetRequest), align=36),
+                         lambda pkt: pkt.message_type == 0x49),
+        ConditionalField(PadField(PacketField("omci_message", None,
+                                              OMCIGetResponse), align=36),
+                         lambda pkt: pkt.message_type == 0x29),
+        # TODO add additional message types here as padded conditionals...
+        IntField("omci_trailer", 0x00000028)
+    ]
diff --git a/voltha/protos/third_party/__init__.py b/voltha/protos/third_party/__init__.py
index e69de29..19192c4 100644
--- a/voltha/protos/third_party/__init__.py
+++ b/voltha/protos/third_party/__init__.py
@@ -0,0 +1,50 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+This helps loading http_pb2 and annotations_pb2.
+Without this, the Python importer will not be able to process the lines:
+from google.api import http_pb2 or
+from google.api import annotations_pb2
+(Without importing these, the protobuf loader will not recognize http options
+in the protobuf definitions.)
+"""
+
+from importlib import import_module
+import os
+import sys
+
+
+class GoogleApiImporter(object):
+
+    def find_module(self, full_name, path=None):
+        if full_name == 'google.api':
+            self.path = [os.path.dirname(__file__)]
+            return self
+
+    def load_module(self, name):
+        if name in sys.modules:
+            return sys.modules[name]
+        full_name = 'chameleon.protos.third_party.' + name
+        import_module(full_name)
+        module = sys.modules[full_name]
+        sys.modules[name] = module
+        return module
+
+
+sys.meta_path.append(GoogleApiImporter())
+from google.api import http_pb2, annotations_pb2
+_ = http_pb2, annotations_pb2