SEBA-927 implemenation of controlled PON and ONU activation
updated controlledActivation to enum

Change-Id: I63ba732338f3df79f01a30703d9e6118d95bf8aa
diff --git a/cmd/bbr/bbr.go b/cmd/bbr/bbr.go
index 6ffcce6..4c83789 100644
--- a/cmd/bbr/bbr.go
+++ b/cmd/bbr/bbr.go
@@ -77,6 +77,7 @@
 		true, // this parameter is not important in the BBR Case
 		true, // this parameter is not important in the BBR Case
 		0,    // this parameter does not matter in the BBR case
+		options.BBSim.ControlledActivation,
 		true,
 	)
 	oltMock := bbrdevices.OltMock{
diff --git a/cmd/bbsim/bbsim.go b/cmd/bbsim/bbsim.go
index 8ffb941..f2a1c37 100644
--- a/cmd/bbsim/bbsim.go
+++ b/cmd/bbsim/bbsim.go
@@ -146,14 +146,15 @@
 	}
 
 	log.WithFields(log.Fields{
-		"OltID":        options.Olt.ID,
-		"NumNniPerOlt": options.Olt.NniPorts,
-		"NumPonPerOlt": options.Olt.PonPorts,
-		"NumOnuPerPon": options.Olt.OnusPonPort,
-		"TotalOnus":    options.Olt.PonPorts * options.Olt.OnusPonPort,
-		"EnableAuth":   options.BBSim.EnableAuth,
-		"Dhcp":         options.BBSim.EnableDhcp,
-		"Delay":        options.BBSim.Delay,
+		"OltID":                options.Olt.ID,
+		"NumNniPerOlt":         options.Olt.NniPorts,
+		"NumPonPerOlt":         options.Olt.PonPorts,
+		"NumOnuPerPon":         options.Olt.OnusPonPort,
+		"TotalOnus":            options.Olt.PonPorts * options.Olt.OnusPonPort,
+		"EnableAuth":           options.BBSim.EnableAuth,
+		"Dhcp":                 options.BBSim.EnableDhcp,
+		"Delay":                options.BBSim.Delay,
+		"ControlledActivation": options.BBSim.ControlledActivation,
 	}).Info("BroadBand Simulator is on")
 
 	// control channels, they are only closed when the goroutine needs to be terminated
@@ -169,6 +170,7 @@
 		options.BBSim.EnableAuth,
 		options.BBSim.EnableDhcp,
 		options.BBSim.Delay,
+		options.BBSim.ControlledActivation,
 		false,
 	)
 
diff --git a/configs/bbsim.yaml b/configs/bbsim.yaml
index 4e087cc..77ffa62 100644
--- a/configs/bbsim.yaml
+++ b/configs/bbsim.yaml
@@ -9,6 +9,7 @@
   openolt_address: ":50060"
   api_address: ":50070"
   rest_api_address: ":50071"
+  controlled_activation: "default"
   # legacy_api_address: ":50072"
   # legacy_rest_api_address: ":50073"
   # sadis_rest_address: ":50073"
diff --git a/internal/bbsim/api/onus_handler.go b/internal/bbsim/api/onus_handler.go
index cec8803..7ec4ab3 100644
--- a/internal/bbsim/api/onus_handler.go
+++ b/internal/bbsim/api/onus_handler.go
@@ -136,13 +136,39 @@
 	olt := devices.GetOLT()
 
 	onu, err := olt.FindOnuBySn(req.SerialNumber)
-
 	if err != nil {
 		res.StatusCode = int32(codes.NotFound)
 		res.Message = err.Error()
 		return res, err
 	}
 
+	pon, _ := olt.GetPonById(onu.PonPortID)
+	if pon.InternalState.Current() != "enabled" {
+		err := fmt.Errorf("PON port %d not enabled", onu.PonPortID)
+		logger.WithFields(log.Fields{
+			"OnuId":  onu.ID,
+			"IntfId": onu.PonPortID,
+			"OnuSn":  onu.Sn(),
+		}).Errorf("Cannot poweron ONU: %s", err.Error())
+
+		res.StatusCode = int32(codes.FailedPrecondition)
+		res.Message = err.Error()
+		return res, err
+	}
+
+	if onu.InternalState.Current() == "created" {
+		if err := onu.InternalState.Event("initialize"); err != nil {
+			logger.WithFields(log.Fields{
+				"OnuId":  onu.ID,
+				"IntfId": onu.PonPortID,
+				"OnuSn":  onu.Sn(),
+			}).Errorf("Cannot poweron ONU: %s", err.Error())
+			res.StatusCode = int32(codes.FailedPrecondition)
+			res.Message = err.Error()
+			return res, err
+		}
+	}
+
 	if err := onu.InternalState.Event("discover"); err != nil {
 		logger.WithFields(log.Fields{
 			"OnuId":  onu.ID,
diff --git a/internal/bbsim/devices/helpers.go b/internal/bbsim/devices/helpers.go
index 07cfe3a..5a27202 100644
--- a/internal/bbsim/devices/helpers.go
+++ b/internal/bbsim/devices/helpers.go
@@ -22,6 +22,24 @@
 	"strconv"
 )
 
+type mode int
+
+// Constants for Controlled Activation modes
+const (
+	Default mode = iota
+	OnlyONU
+	OnlyPON
+	Both
+)
+
+// ControlledActivationModes maps string to int value of mode
+var ControlledActivationModes = map[string]mode{
+	"default":  Default,
+	"only-onu": OnlyONU,
+	"only-pon": OnlyPON,
+	"both":     Both,
+}
+
 var newFSM = fsm.NewFSM
 
 func getOperStateFSM(cb fsm.Callback) *fsm.FSM {
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index 016f148..f68ee9e 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -47,17 +47,17 @@
 	sync.Mutex
 
 	// BBSIM Internals
-	ID              int
-	SerialNumber    string
-	NumNni          int
-	NumPon          int
-	NumOnuPerPon    int
-	InternalState   *fsm.FSM
-	channel         chan Message
-	nniPktInChannel chan *bbsim.PacketMsg // packets coming in from the NNI and going to VOLTHA
-	nniHandle       *pcap.Handle          // handle on the NNI interface, close it when shutting down the NNI channel
-
-	Delay int
+	ID                   int
+	SerialNumber         string
+	NumNni               int
+	NumPon               int
+	NumOnuPerPon         int
+	InternalState        *fsm.FSM
+	channel              chan Message
+	nniPktInChannel      chan *bbsim.PacketMsg // packets coming in from the NNI and going to VOLTHA
+	nniHandle            *pcap.Handle          // handle on the NNI interface, close it when shutting down the NNI channel
+	Delay                int
+	ControlledActivation mode
 
 	Pons []*PonPort
 	Nnis []*NniPort
@@ -78,7 +78,7 @@
 	return &olt
 }
 
-func CreateOLT(oltId int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, auth bool, dhcp bool, delay int, isMock bool) *OltDevice {
+func CreateOLT(oltId int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, auth bool, dhcp bool, delay int, ca string, isMock bool) *OltDevice {
 	oltLogger.WithFields(log.Fields{
 		"ID":           oltId,
 		"NumNni":       nni,
@@ -100,6 +100,13 @@
 		Delay:        delay,
 	}
 
+	if val, ok := ControlledActivationModes[ca]; ok {
+		olt.ControlledActivation = val
+	} else {
+		oltLogger.Warn("Unknown ControlledActivation Mode given, running in Default mode")
+		olt.ControlledActivation = Default
+	}
+
 	// OLT State machine
 	// NOTE do we need 2 state machines for the OLT? (InternalState and OperState)
 	olt.InternalState = fsm.NewFSM(
@@ -132,7 +139,7 @@
 	// create PON ports
 	availableCTag := cTagInit
 	for i := 0; i < pon; i++ {
-		p := CreatePonPort(olt, uint32(i))
+		p := CreatePonPort(&olt, uint32(i))
 
 		// create ONU devices
 		for j := 0; j < onuPerPon; j++ {
@@ -209,22 +216,33 @@
 	// TODO handle hard poweroff (i.e. no indications sent to Voltha) vs soft poweroff
 	time.Sleep(1 * time.Second) // we need to give the OLT the time to respond to all the pending gRPC request before stopping the server
 	if err := o.StopOltServer(); err != nil {
+		oltLogger.Errorf("Error in stopping OLT server")
 		return err
 	}
 
+	for _, pon := range olt.Pons {
+		msg := Message{
+			Type: PonIndication,
+			Data: PonIndicationMessage{
+				OperState: DOWN,
+				PonPortID: pon.ID,
+			},
+		}
+		o.channel <- msg
+
+		for _, onu := range pon.Onus {
+			if onu.InternalState.Current() != "initialized" {
+				onu.InternalState.Event("disable")
+			}
+		}
+	}
+
 	// terminate the OLT's processOltMessages go routine
 	close(o.channel)
 	// terminate the OLT's processNniPacketIns go routine
 	go o.nniHandle.Close()
 	close(o.nniPktInChannel)
 
-	for i := range olt.Pons {
-		for _, onu := range olt.Pons[i].Onus {
-			// NOTE while the olt is off, restore the ONU to the initial state
-			onu.InternalState.SetState("created")
-		}
-	}
-
 	time.Sleep(time.Duration(rebootDelay) * time.Second)
 
 	if err := o.InternalState.Event("initialize"); err != nil {
@@ -277,6 +295,7 @@
 // Enable implements the OpenOLT EnableIndicationServer functionality
 func (o *OltDevice) Enable(stream openolt.Openolt_EnableIndicationServer) error {
 	oltLogger.Debug("Enable OLT called")
+	rebootFlag := false
 
 	// If enabled has already been called then an enabled context has
 	// been created. If this is the case then we want to cancel all the
@@ -285,6 +304,7 @@
 	o.Lock()
 	if o.enableContext != nil && o.enableContextCancel != nil {
 		o.enableContextCancel()
+		rebootFlag = true
 	}
 	o.enableContext, o.enableContextCancel = context.WithCancel(context.TODO())
 	o.Unlock()
@@ -321,25 +341,35 @@
 
 	go o.processOmciMessages(o.enableContext, stream, &wg)
 
-	// send PON Port indications
-	for i, pon := range o.Pons {
-		msg := Message{
-			Type: PonIndication,
-			Data: PonIndicationMessage{
-				OperState: UP,
-				PonPortID: pon.ID,
-			},
-		}
-		o.channel <- msg
-
-		for _, onu := range o.Pons[i].Onus {
-			if err := onu.InternalState.Event("initialize"); err != nil {
-				log.Errorf("Error initializing ONU: %v", err)
-				continue
+	if rebootFlag == true {
+		for _, pon := range o.Pons {
+			if pon.InternalState.Current() == "disabled" {
+				msg := Message{
+					Type: PonIndication,
+					Data: PonIndicationMessage{
+						OperState: UP,
+						PonPortID: pon.ID,
+					},
+				}
+				o.channel <- msg
 			}
-			if err := onu.InternalState.Event("discover"); err != nil {
-				log.Errorf("Error discover ONU: %v", err)
-				return err
+		}
+	} else {
+
+		// 1. controlledActivation == Default: Send both PON and ONUs indications
+		// 2. controlledActivation == only-onu: that means only ONUs will be controlled activated, so auto send PON indications
+
+		if o.ControlledActivation == Default || o.ControlledActivation == OnlyONU {
+			// send PON Port indications
+			for _, pon := range o.Pons {
+				msg := Message{
+					Type: PonIndication,
+					Data: PonIndicationMessage{
+						OperState: UP,
+						PonPortID: pon.ID,
+					},
+				}
+				o.channel <- msg
 			}
 		}
 	}
@@ -468,25 +498,11 @@
 	}).Debug("Sent Indication_IntfOperInd for NNI")
 }
 
-func (o *OltDevice) sendPonIndication(msg PonIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
-	pon, _ := o.GetPonById(msg.PonPortID)
-	if msg.OperState == UP {
-		if err := pon.OperState.Event("enable"); err != nil {
-			log.WithFields(log.Fields{
-				"Type":      pon.Type,
-				"IntfId":    pon.ID,
-				"OperState": pon.OperState.Current(),
-			}).Errorf("Can't move PON Port to enable state: %v", err)
-		}
-	} else if msg.OperState == DOWN {
-		if err := pon.OperState.Event("disable"); err != nil {
-			log.WithFields(log.Fields{
-				"Type":      pon.Type,
-				"IntfId":    pon.ID,
-				"OperState": pon.OperState.Current(),
-			}).Errorf("Can't move PON Port to disable state: %v", err)
-		}
-	}
+func (o *OltDevice) sendPonIndication(ponPortID uint32) {
+
+	stream := *o.OpenoltStream
+	pon, _ := o.GetPonById(ponPortID)
+	// Send IntfIndication for PON port
 	discoverData := &openolt.Indication_IntfInd{IntfInd: &openolt.IntfIndication{
 		IntfId:    pon.ID,
 		OperState: pon.OperState.Current(),
@@ -502,6 +518,7 @@
 		"OperState": pon.OperState.Current(),
 	}).Debug("Sent Indication_IntfInd")
 
+	// Send IntfOperIndication for PON port
 	operData := &openolt.Indication_IntfOperInd{IntfOperInd: &openolt.IntfOperIndication{
 		Type:      pon.Type,
 		IntfId:    pon.ID,
@@ -561,7 +578,14 @@
 				o.sendNniIndication(msg, stream)
 			case PonIndication:
 				msg, _ := message.Data.(PonIndicationMessage)
-				o.sendPonIndication(msg, stream)
+				pon, _ := o.GetPonById(msg.PonPortID)
+				if msg.OperState == UP {
+					pon.OperState.Event("enable")
+					pon.InternalState.Event("enable")
+				} else if msg.OperState == DOWN {
+					pon.OperState.Event("disable")
+					pon.InternalState.Event("disable")
+				}
 			default:
 				oltLogger.Warnf("Received unknown message data %v for type %v in OLT Channel", message.Data, message.Type)
 			}
@@ -645,26 +669,6 @@
 	}).Warn("Stopped handling NNI Channel")
 }
 
-func (o *OltDevice) handleReenableOlt() {
-	// enable OLT
-	oltMsg := Message{
-		Type: OltIndication,
-		Data: OltIndicationMessage{
-			OperState: UP,
-		},
-	}
-	o.channel <- oltMsg
-
-	for i := range olt.Pons {
-		for _, onu := range olt.Pons[i].Onus {
-			if err := onu.InternalState.Event("discover"); err != nil {
-				log.Errorf("Error discover ONU: %v", err)
-			}
-		}
-	}
-
-}
-
 // returns an ONU with a given Serial Number
 func (o OltDevice) FindOnuBySn(serialNumber string) (*Onu, error) {
 	// TODO this function can be a performance bottleneck when we have many ONUs,
@@ -788,16 +792,17 @@
 	}).Info("Disabling OLT")
 
 	for _, pon := range o.Pons {
-		// disable PONs
-		msg := Message{
-			Type: PonIndication,
-			Data: PonIndicationMessage{
-				OperState: DOWN,
-				PonPortID: pon.ID,
-			},
+		if pon.InternalState.Current() == "enabled" {
+			// disable PONs
+			msg := Message{
+				Type: PonIndication,
+				Data: PonIndicationMessage{
+					OperState: DOWN,
+					PonPortID: pon.ID,
+				},
+			}
+			o.channel <- msg
 		}
-
-		o.channel <- msg
 	}
 
 	// Note that we are not disabling the NNI as the real OLT does not.
@@ -825,8 +830,18 @@
 	return nil
 }
 
-func (o OltDevice) EnablePonIf(context.Context, *openolt.Interface) (*openolt.Empty, error) {
-	oltLogger.Error("EnablePonIf not implemented")
+func (o OltDevice) EnablePonIf(_ context.Context, intf *openolt.Interface) (*openolt.Empty, error) {
+	oltLogger.Errorf("EnablePonIf request received for PON %d", intf.IntfId)
+	ponID := intf.GetIntfId()
+	msg := Message{
+		Type: PonIndication,
+		Data: PonIndicationMessage{
+			OperState: UP,
+			PonPortID: ponID,
+		},
+	}
+	o.channel <- msg
+
 	return new(openolt.Empty), nil
 }
 
@@ -995,20 +1010,27 @@
 		"oltId": o.ID,
 	}).Info("Received ReenableOlt request from VOLTHA")
 
-	for _, pon := range o.Pons {
-		msg := Message{
-			Type: PonIndication,
-			Data: PonIndicationMessage{
-				OperState: UP,
-				PonPortID: pon.ID,
-			},
-		}
-		o.channel <- msg
+	// enable OLT
+	oltMsg := Message{
+		Type: OltIndication,
+		Data: OltIndicationMessage{
+			OperState: UP,
+		},
 	}
+	o.channel <- oltMsg
 
-	// Openolt adapter will start processing indications only after success reponse of ReenableOlt
-	// thats why need to send OLT and ONU indications after return of this function
-	go o.handleReenableOlt()
+	for _, pon := range o.Pons {
+		if pon.InternalState.Current() == "disabled" {
+			msg := Message{
+				Type: PonIndication,
+				Data: PonIndicationMessage{
+					OperState: UP,
+					PonPortID: pon.ID,
+				},
+			}
+			o.channel <- msg
+		}
+	}
 
 	return new(openolt.Empty), nil
 }
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index c9c2f17..afa297c 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -82,7 +82,7 @@
 func CreateONU(olt *OltDevice, pon PonPort, id uint32, sTag int, cTag int, auth bool, dhcp bool, isMock bool) *Onu {
 
 	o := Onu{
-		ID:                  id,
+		ID:                  0,
 		PonPortID:           pon.ID,
 		PonPort:             pon,
 		STag:                sTag,
@@ -98,7 +98,7 @@
 		DhcpFlowReceived:    false,
 		DiscoveryRetryDelay: 60 * time.Second, // this is used to send OnuDiscoveryIndications until an activate call is received
 	}
-	o.SerialNumber = o.NewSN(olt.ID, pon.ID, o.ID)
+	o.SerialNumber = o.NewSN(olt.ID, pon.ID, id)
 
 	// NOTE this state machine is used to track the operational
 	// state as requested by VOLTHA
@@ -119,7 +119,7 @@
 			{Name: "receive_eapol_flow", Src: []string{"enabled", "gem_port_added"}, Dst: "eapol_flow_received"},
 			{Name: "add_gem_port", Src: []string{"enabled", "eapol_flow_received"}, Dst: "gem_port_added"},
 			// NOTE should disabled state be different for oper_disabled (emulating an error) and admin_disabled (received a disabled call via VOLTHA)?
-			{Name: "disable", Src: []string{"enabled", "eapol_flow_received", "gem_port_added", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed"}, Dst: "disabled"},
+			{Name: "disable", Src: []string{"enabled", "eapol_flow_received", "gem_port_added", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed", "pon_disabled"}, Dst: "disabled"},
 			// ONU state when PON port is disabled but ONU is power ON(more states should be added in src?)
 			{Name: "pon_disabled", Src: []string{"enabled", "gem_port_added", "eapol_flow_received", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed"}, Dst: "pon_disabled"},
 			// EAPOL
@@ -303,7 +303,7 @@
 			case OnuDiscIndication:
 				msg, _ := message.Data.(OnuDiscIndicationMessage)
 				// NOTE we need to slow down and send ONU Discovery Indication in batches to better emulate a real scenario
-				time.Sleep(time.Duration(int(o.ID)*o.PonPort.Olt.Delay) * time.Millisecond)
+				time.Sleep(time.Duration(o.PonPort.Olt.Delay) * time.Millisecond)
 				o.sendOnuDiscIndication(msg, stream)
 			case OnuIndication:
 				msg, _ := message.Data.(OnuIndicationMessage)
diff --git a/internal/bbsim/devices/onu_test_helpers.go b/internal/bbsim/devices/onu_test_helpers.go
index cba8db6..a99a007 100644
--- a/internal/bbsim/devices/onu_test_helpers.go
+++ b/internal/bbsim/devices/onu_test_helpers.go
@@ -127,7 +127,8 @@
 		ID: 0,
 	}
 	pon := PonPort{
-		ID: 1,
+		ID:  1,
+		Olt: &olt,
 	}
 	onu := CreateONU(&olt, pon, 1, 900, 900, false, false, true)
 	// NOTE we need this in order to create the OnuChannel
diff --git a/internal/bbsim/devices/pon.go b/internal/bbsim/devices/pon.go
index 24b541b..130c74f 100644
--- a/internal/bbsim/devices/pon.go
+++ b/internal/bbsim/devices/pon.go
@@ -31,17 +31,18 @@
 	ID     uint32
 	NumOnu int
 	Onus   []*Onu
-	Olt    OltDevice
+	Olt    *OltDevice
 
 	// PON Attributes
 	OperState *fsm.FSM
 	Type      string
 
 	// NOTE do we need a state machine for the PON Ports?
+	InternalState *fsm.FSM
 }
 
 // CreatePonPort creates pon port object
-func CreatePonPort(olt OltDevice, id uint32) *PonPort {
+func CreatePonPort(olt *OltDevice, id uint32) *PonPort {
 
 	ponPort := PonPort{
 		NumOnu: olt.NumOnuPerPon,
@@ -51,6 +52,61 @@
 		Onus:   []*Onu{},
 	}
 
+	ponPort.InternalState = fsm.NewFSM(
+		"created",
+		fsm.Events{
+			{Name: "enable", Src: []string{"created", "disabled"}, Dst: "enabled"},
+			{Name: "disable", Src: []string{"enabled"}, Dst: "disabled"},
+		},
+		fsm.Callbacks{
+			"enter_enabled": func(e *fsm.Event) {
+				oltLogger.WithFields(log.Fields{
+					"ID": ponPort.ID,
+				}).Debugf("Changing PON Port InternalState from %s to %s", e.Src, e.Dst)
+
+				if e.Src == "created" {
+					if olt.ControlledActivation == Default || olt.ControlledActivation == OnlyPON {
+						for _, onu := range ponPort.Onus {
+							if err := onu.InternalState.Event("initialize"); err != nil {
+								log.Errorf("Error initializing ONU: %v", err)
+								continue
+							}
+							if err := onu.InternalState.Event("discover"); err != nil {
+								log.Errorf("Error discover ONU: %v", err)
+							}
+						}
+					}
+				} else if e.Src == "disabled" {
+					for _, onu := range ponPort.Onus {
+						if onu.InternalState.Current() == "pon_disabled" {
+							if err := onu.InternalState.Event("discover"); err != nil {
+								log.Errorf("Error discover ONU: %v", err)
+							}
+						} else if onu.InternalState.Current() == "disabled" {
+							if err := onu.InternalState.Event("initialize"); err != nil {
+								log.Errorf("Error initialize ONU: %v", err)
+								continue
+							}
+							if err := onu.InternalState.Event("discover"); err != nil {
+								log.Errorf("Error discover ONU: %v", err)
+							}
+						}
+					}
+				}
+			},
+			"enter_disabled": func(e *fsm.Event) {
+				for _, onu := range ponPort.Onus {
+					if onu.InternalState.Current() == "initialized" {
+						continue
+					}
+					if err := onu.InternalState.Event("pon_disabled"); err != nil {
+						oltLogger.Errorf("Failed to move ONU in pon_disabled states: %v", err)
+					}
+				}
+			},
+		},
+	)
+
 	ponPort.OperState = fsm.NewFSM(
 		"down",
 		fsm.Events{
@@ -62,17 +118,13 @@
 				oltLogger.WithFields(log.Fields{
 					"ID": ponPort.ID,
 				}).Debugf("Changing PON Port OperState from %s to %s", e.Src, e.Dst)
+				olt.sendPonIndication(ponPort.ID)
 			},
 			"enter_down": func(e *fsm.Event) {
 				oltLogger.WithFields(log.Fields{
 					"ID": ponPort.ID,
 				}).Debugf("Changing PON Port OperState from %s to %s", e.Src, e.Dst)
-
-				for _, onu := range ponPort.Onus {
-					if err := onu.InternalState.Event("pon_disabled"); err != nil {
-						oltLogger.Errorf("Failed to move ONU in pon_disabled states: %v", err)
-					}
-				}
+				olt.sendPonIndication(ponPort.ID)
 			},
 		},
 	)
diff --git a/internal/common/options.go b/internal/common/options.go
index 361d9b3..648c5cf 100644
--- a/internal/common/options.go
+++ b/internal/common/options.go
@@ -70,6 +70,7 @@
 	LegacyRestApiAddress string  `yaml:"legacy_rest_api_address"`
 	SadisRestAddress     string  `yaml:"sadis_rest_address"`
 	SadisServer          bool    `yaml:"sadis_server"`
+	ControlledActivation string  `yaml:"controlled_activation"`
 }
 
 type BBRConfig struct {
@@ -103,6 +104,7 @@
 			LegacyRestApiAddress: ":50073",
 			SadisRestAddress:     ":50074",
 			SadisServer:          true,
+			ControlledActivation: "default",
 		},
 		OltConfig{
 			Vendor:             "BBSim",
@@ -165,6 +167,7 @@
 
 	delay := flag.Int("delay", conf.BBSim.Delay, "The delay between ONU DISCOVERY batches in milliseconds (1 ONU per each PON PORT at a time")
 
+	controlledActivation := flag.String("ca", conf.BBSim.ControlledActivation, "Set the mode for controlled activation of PON ports and ONUs")
 	flag.Parse()
 
 	conf.Olt.ID = int(*olt_id)
@@ -179,6 +182,7 @@
 	conf.BBSim.EnableAuth = *auth
 	conf.BBSim.EnableDhcp = *dhcp
 	conf.BBSim.Delay = *delay
+	conf.BBSim.ControlledActivation = *controlledActivation
 
 	// update device id if not set
 	if conf.Olt.DeviceId == "" {