Adding ability to send OMCI messages from PMCS adapter

Change-Id: Ic6eacc4e74cc2796f93c5787fa57dc230dd11036
diff --git a/voltha/adapters/microsemi_olt/ActivationWatcher.py b/voltha/adapters/microsemi_olt/ActivationWatcher.py
index ee0e309..c59c19c 100644
--- a/voltha/adapters/microsemi_olt/ActivationWatcher.py
+++ b/voltha/adapters/microsemi_olt/ActivationWatcher.py
@@ -153,34 +153,19 @@
     Utility Methods
     """
 
-    def create_port(self, vendor):
-        port = Port(port_no=self.port_id,
-                    label="{} ONU".format(vendor),
-                    type=Port.ETHERNET_UNI,
-                    admin_state=AdminState.ENABLED,
-                    oper_status=OperStatus.ACTIVE
-        )
-        self.device.add_port(port)
-
     def px(self, pkt):
         return self.p(pkt, channel_id=self.channel_id,
                       onu_id=self.onu_id, onu_session_id=self.onu_session_id)
 
-    def error(self, msg):
-        log.error(msg)
-        raise self.error()
-
     def detect_onu(self):
         log.info("Activated {} ONT".format(self.vendor))
-        self.create_port(self.vendor)
-        print self.channel_id
-        print self.onu_id
         try:
             self.device.onu_detected(
                 parent_port_no=self.channel_id,
                 child_device_type='%s_onu' % self.vendor.lower(),
                 onu_id=self.onu_id,
-                serial_number=hexstring(self.serial_number)
+                serial_number=hexstring(self.serial_number),
+                onu_session_id=self.onu_session_id
             )
         except Exception as e:
             print e
@@ -443,6 +428,7 @@
     def wait_for_set_vlan_uplink_config(self, pkt):
         if PAS5211SetVlanUplinkConfigurationResponse in pkt:
             # YAY we made it.
+            # TODO update OLT with CNI port
             self.detect_onu()
             raise self.end()
 
diff --git a/voltha/adapters/microsemi_olt/DeviceManager.py b/voltha/adapters/microsemi_olt/DeviceManager.py
index e468a0d..a9795d4 100644
--- a/voltha/adapters/microsemi_olt/DeviceManager.py
+++ b/voltha/adapters/microsemi_olt/DeviceManager.py
@@ -105,7 +105,8 @@
     def onu_detected(self, parent_port_no=None,
                         child_device_type=None,
                         onu_id=None,
-                        serial_number=None):
+                        serial_number=None,
+                        onu_session_id=None):
         self.adapter_agent.child_device_detected(
             parent_device_id=self.device.id,
             parent_port_no=parent_port_no,
@@ -113,7 +114,9 @@
             serial_number=serial_number,
             proxy_address=Device.ProxyAddress(
                 device_id=self.device.id,
-                channel_id=onu_id
+                channel_id=parent_port_no, # happens to be the channel id as well
+                onu_id=onu_id,
+                onu_session_id=onu_session_id
             ),
             vlan=0
         )
diff --git a/voltha/adapters/microsemi_olt/OMCIProxy.py b/voltha/adapters/microsemi_olt/OMCIProxy.py
new file mode 100644
index 0000000..be81fd4
--- /dev/null
+++ b/voltha/adapters/microsemi_olt/OMCIProxy.py
@@ -0,0 +1,107 @@
+#
+# Copyright 2017 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.
+#
+
+"""
+Handles sequence of messages that are used to send OMCI to the ONU
+"""
+import structlog
+from scapy.automaton import ATMT
+
+from voltha.adapters.microsemi_olt.BaseOltAutomaton import BaseOltAutomaton
+from voltha.adapters.microsemi_olt.PAS5211 import PAS5211MsgSendFrame, PAS5211MsgSendFrameResponse, \
+    PAS5211EventFrameReceived
+
+log = structlog.get_logger()
+
+class OMCIProxy(BaseOltAutomaton):
+
+
+    proxy_address = None
+    msg = None
+
+    def parse_args(self, debug=0, store=0, **kwargs):
+        self.adaptor_agent = kwargs.pop('adapter_agent')
+        self.proxy_address = kwargs.pop('proxy_address')
+        self.msg = kwargs.pop('msg')
+
+        BaseOltAutomaton.parse_args(self, debug=debug, store=store, **kwargs)
+
+    """
+    States
+    """
+
+    @ATMT.state(initial=1)
+    def got_omci_msg(self):
+        pass
+
+    @ATMT.state()
+    def wait_send_response(self):
+        pass
+
+    @ATMT.state()
+    def wait_event(self):
+        pass
+
+    @ATMT.state(error=1)
+    def error(self, msg):
+        log.error(msg)
+
+    @ATMT.state(final=1)
+    def end(self):
+        pass
+
+    """
+    Utils
+    """
+
+    def px(self, pkt):
+        return self.p(pkt, channel_id=self.proxy_address.channel_id,
+                      onu_id=self.proxy_address.onu_id,
+                      onu_session_id=self.proxy_address.onu_session_id)
+
+    """
+    Transitions
+    """
+
+    @ATMT.condition(got_omci_msg)
+    def send_omci_msg(self):
+        pkt = PAS5211MsgSendFrame(frame=self.msg, port_id=self.proxy_address.onu_id)
+        self.send(self.px(pkt))
+        raise self.wait_send_response()
+
+    # Transitions from wait_send_response
+    @ATMT.timeout(wait_send_response, 3)
+    def timeout_wait_send_response(self):
+        raise self.error("No ack for OMCI for {}".format(self.proxy_address))
+
+    @ATMT.receive_condition(wait_send_response)
+    def wait_for_send_response(self, pkt):
+        if PAS5211MsgSendFrameResponse in pkt:
+            raise self.wait_event()
+
+    # Transitions from wait_event
+    @ATMT.timeout(wait_event, 3)
+    def timeout_wait_event(self):
+        raise self.error("No OMCI event for {}".format(self.proxy_address))
+
+    @ATMT.receive_condition(wait_event)
+    def wait_for_event(self, pkt):
+        if PAS5211EventFrameReceived in pkt:
+            # FIXME we may need to verify the transaction id
+            #  to make sure we have the right packet
+            self.adaptor_agent.recieve_proxied_message(self.proxy_address,
+                                                       pkt['PAS5211EventFrameReceived'])
+            raise self.end()
\ No newline at end of file
diff --git a/voltha/adapters/microsemi_olt/microsemi_olt.py b/voltha/adapters/microsemi_olt/microsemi_olt.py
index addfd34..532915d 100644
--- a/voltha/adapters/microsemi_olt/microsemi_olt.py
+++ b/voltha/adapters/microsemi_olt/microsemi_olt.py
@@ -27,6 +27,7 @@
 from voltha.adapters.interface import IAdapterInterface
 from voltha.adapters.microsemi_olt.ActivationWatcher import ActivationWatcher
 from voltha.adapters.microsemi_olt.DeviceManager import DeviceManager
+from voltha.adapters.microsemi_olt.OMCIProxy import OMCIProxy
 from voltha.adapters.microsemi_olt.OltStateMachine import OltStateMachine
 from voltha.adapters.microsemi_olt.PAS5211_comm import PAS5211Communication
 from voltha.protos import third_party
@@ -132,6 +133,11 @@
 
     def send_proxied_message(self, proxy_address, msg):
         log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
+        # TODO make this more efficient
+        omci_proxy = OMCIProxy(proxy_address=proxy_address,
+                               msg=msg)
+        omci_proxy.run()
+        del omci_proxy
 
     def receive_proxied_message(self, proxy_address, msg):
         raise NotImplementedError()
diff --git a/voltha/adapters/pmcs_onu/pmcs_onu.py b/voltha/adapters/pmcs_onu/pmcs_onu.py
index 8b244b6..26b112c 100644
--- a/voltha/adapters/pmcs_onu/pmcs_onu.py
+++ b/voltha/adapters/pmcs_onu/pmcs_onu.py
@@ -20,15 +20,19 @@
 
 import structlog
 from twisted.internet import reactor
+from twisted.internet.defer import DeferredQueue, inlineCallbacks
 from zope.interface import implementer
 
 from voltha.adapters.interface import IAdapterInterface
+from voltha.adapters.microsemi_olt.DeviceManager import mac_str_to_tuple
 from voltha.protos import third_party
 from voltha.protos.adapter_pb2 import Adapter
 from voltha.protos.adapter_pb2 import AdapterConfig
-from voltha.protos.common_pb2 import LogLevel
-from voltha.protos.device_pb2 import DeviceType, DeviceTypes
+from voltha.protos.common_pb2 import LogLevel, ConnectStatus, AdminState, OperStatus
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port
 from voltha.protos.health_pb2 import HealthStatus
+from voltha.protos.logical_device_pb2 import LogicalPort
+from voltha.protos.openflow_13_pb2 import OFPPF_1GB_FD, OFPPF_FIBER, ofp_port, OFPPS_LIVE
 
 _ = third_party
 log = structlog.get_logger()
@@ -55,6 +59,7 @@
             version='0.1',
             config=AdapterConfig(log_level=LogLevel.INFO)
         )
+        self.incoming_messages = DeferredQueue()
 
     def start(self):
         log.debug('starting')
@@ -77,6 +82,8 @@
         raise NotImplementedError()
 
     def adopt_device(self, device):
+        log.info('adopt-device', device=device)
+        reactor.callLater(0.1, self._onu_device_activation, device)
         return device
 
     def abandon_device(self, device):
@@ -116,7 +123,89 @@
     def receive_proxied_message(self, proxy_address, msg):
         log.info('receive-proxied-message', proxy_address=proxy_address,
                  device_id=proxy_address.device_id, msg=msg)
+        self.incoming_messages.put(msg)
 
     def receive_packet_out(self, logical_device_id, egress_port_no, msg):
         log.info('packet-out', logical_device_id=logical_device_id,
                  egress_port_no=egress_port_no, msg_len=len(msg))
+
+    @inlineCallbacks
+    def _onu_device_activation(self, device):
+        # first we verify that we got parent reference and proxy info
+        assert device.parent_id
+        assert device.proxy_address.device_id
+        assert device.proxy_address.channel_id
+
+        device.model = 'GPON ONU'
+        device.hardware_version = 'tbd'
+        device.firware_version = 'tbd'
+        device.software_version = 'tbd'
+
+        device.connect_status = ConnectStatus.REACHABLE
+
+        self.adapter_agent.update_device(device)
+
+        uni_port = Port(port_no=self.port_id,
+                    label="{} ONU".format('PMCS'),
+                    type=Port.ETHERNET_UNI,
+                    admin_state=AdminState.ENABLED,
+                    oper_status=OperStatus.ACTIVE
+                    )
+        self.device.add_port(uni_port)
+
+        pon_port = Port(
+            port_no=1,
+            label='PON port',
+            type=Port.PON_ONU,
+            admin_state=AdminState.ENABLED,
+            oper_status=OperStatus.ACTIVE,
+            peers=[
+                Port.PeerPort(
+                    device_id=device.parent_id,
+                    port_no=device.parent_port_no
+                )
+            ]
+        )
+
+        self.adapter_agent.add_port(device.id, pon_port)
+
+        # obtain logical device id
+        parent_device = self.adapter_agent.get_device(device.parent_id)
+        logical_device_id = parent_device.parent_id
+        assert logical_device_id
+
+        # we are going to use the proxy_address.channel_id as unique number
+        # and name for the virtual ports, as this is guaranteed to be unique
+        # in the context of the OLT port, so it is also unique in the context
+        # of the logical device
+        port_no = device.proxy_address.channel_id
+        cap = OFPPF_1GB_FD | OFPPF_FIBER
+        self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
+            id=str(port_no),
+            ofp_port=ofp_port(
+                port_no=port_no,
+                hw_addr=mac_str_to_tuple(device.mac_address),
+                name='uni-{}'.format(port_no),
+                config=0,
+                state=OFPPS_LIVE,
+                curr=cap,
+                advertised=cap,
+                peer=cap,
+                curr_speed=OFPPF_1GB_FD,
+                max_speed=OFPPF_1GB_FD
+            ),
+            device_id=device.id,
+            device_port_no=uni_port.port_no
+        ))
+
+        self._initialize_onu(device)
+
+        # and finally update to "ACTIVE"
+        device = self.adapter_agent.get_device(device.id)
+        device.oper_status = OperStatus.ACTIVE
+        self.adapter_agent.update_device(device)
+
+
+    def _initialize_onu(self, device):
+        # DO things to the ONU
+        pass
\ No newline at end of file
diff --git a/voltha/protos/device.proto b/voltha/protos/device.proto
index 47deacd..5a32f28 100644
--- a/voltha/protos/device.proto
+++ b/voltha/protos/device.proto
@@ -129,7 +129,9 @@
 
     message ProxyAddress {
         string device_id = 1;  // Which device to use as proxy to this device
-        uint32 channel_id = 2;  // Sub-address within proxy device
+        uint32 channel_id = 2;  // Sub-address within proxy
+        uint32 onu_id = 3; // onu identifier; optional
+        uint32 onu_sesssion_id = 4; // session identifier for the ONU; optional
     };
 
     oneof address {