[VOL-3331] Implement incremental ONU traffic flow setup request with according OMCI VLAN configuration,
now already merged with git merged patch for [VOL-3051] Create MIB template from first ONU + correction TechProfile channel processing

Signed-off-by: mpagenko <michael.pagenkopf@adtran.com>
Change-Id: Iabbf4e1bc16da9c115e8e4002fd328a4c6bf33fb
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
index 2112b85..d47245a 100644
--- a/internal/pkg/config/config.go
+++ b/internal/pkg/config/config.go
@@ -39,6 +39,7 @@
 	defaultLoglevel             = "WARN"
 	defaultBanner               = false
 	defaultDisplayVersionOnly   = false
+	defaultAccIncrEvto          = false
 	defaultTopic                = "openonu"
 	defaultCoretopic            = "rwcore"
 	defaultEventtopic           = "voltha.events"
@@ -76,6 +77,7 @@
 	OnuNumber                   int
 	Banner                      bool
 	DisplayVersionOnly          bool
+	AccIncrEvto                 bool
 	ProbeHost                   string
 	ProbePort                   int
 	LiveProbeInterval           time.Duration
@@ -106,6 +108,7 @@
 		OnuNumber:                   defaultOnunumber,
 		Banner:                      defaultBanner,
 		DisplayVersionOnly:          defaultDisplayVersionOnly,
+		AccIncrEvto:                 defaultAccIncrEvto,
 		ProbeHost:                   defaultProbeHost,
 		ProbePort:                   defaultProbePort,
 		LiveProbeInterval:           defaultLiveProbeInterval,
@@ -167,6 +170,9 @@
 	help = fmt.Sprintf("Show version information and exit")
 	flag.BoolVar(&(so.DisplayVersionOnly), "version", defaultDisplayVersionOnly, help)
 
+	help = fmt.Sprintf("Acceptance of incremental EVTOCD configuration")
+	flag.BoolVar(&(so.AccIncrEvto), "accept_incr_evto", defaultAccIncrEvto, help)
+
 	help = fmt.Sprintf("The address on which to listen to answer liveness and readiness probe queries over HTTP.")
 	flag.StringVar(&(so.ProbeHost), "probe_host", defaultProbeHost, help)
 
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 93dbb6c..395b369 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -32,9 +32,13 @@
 	me "github.com/opencord/omci-lib-go/generated"
 	"github.com/opencord/voltha-lib-go/v3/pkg/adapters/adapterif"
 	"github.com/opencord/voltha-lib-go/v3/pkg/db"
+	flow "github.com/opencord/voltha-lib-go/v3/pkg/flows"
 	"github.com/opencord/voltha-lib-go/v3/pkg/log"
 	vc "github.com/opencord/voltha-protos/v3/go/common"
 	ic "github.com/opencord/voltha-protos/v3/go/inter_container"
+	"github.com/opencord/voltha-protos/v3/go/openflow_13"
+	of "github.com/opencord/voltha-protos/v3/go/openflow_13"
+	ofp "github.com/opencord/voltha-protos/v3/go/openflow_13"
 	oop "github.com/opencord/voltha-protos/v3/go/openolt"
 	"github.com/opencord/voltha-protos/v3/go/voltha"
 )
@@ -123,11 +127,12 @@
 	//onus     sync.Map
 	//portStats          *OpenOltStatisticsMgr
 	//metrics            *pmmetrics.PmMetrics
-	stopCollector      chan bool
-	stopHeartbeatCheck chan bool
-	activePorts        sync.Map
-	uniEntityMap       map[uint32]*OnuUniPort
-	reconciling        bool
+	stopCollector       chan bool
+	stopHeartbeatCheck  chan bool
+	activePorts         sync.Map
+	uniEntityMap        map[uint32]*OnuUniPort
+	UniVlanConfigFsmMap map[uint8]*UniVlanConfigFsm
+	reconciling         bool
 }
 
 //NewDeviceHandler creates a new device handler
@@ -151,6 +156,7 @@
 	dh.activePorts = sync.Map{}
 	//TODO initialize the support classes.
 	dh.uniEntityMap = make(map[uint32]*OnuUniPort)
+	dh.UniVlanConfigFsmMap = make(map[uint8]*UniVlanConfigFsm)
 	dh.reconciling = false
 
 	// Device related state machine
@@ -315,7 +321,7 @@
 				var wg sync.WaitGroup
 				wg.Add(2) // for the 2 go routines to finish
 				// attention: deadline completion check and wg.Done is to be done in both routines
-				go dh.pOnuTP.configureUniTp(dctx, techProfMsg.UniId, techProfMsg.Path, &wg)
+				go dh.pOnuTP.configureUniTp(dctx, uint8(techProfMsg.UniId), techProfMsg.Path, &wg)
 				go dh.pOnuTP.updateOnuTpPathKvStore(dctx, &wg)
 				//the wait.. function is responsible for tpProcMutex.Unlock()
 				err := dh.pOnuTP.waitForTpCompletion(cancel, &wg) //wait for background process to finish and collect their result
@@ -408,6 +414,58 @@
 	return nil
 }
 
+//FlowUpdateIncremental removes and/or adds the flow changes on a given device
+func (dh *DeviceHandler) FlowUpdateIncremental(apOfFlowChanges *openflow_13.FlowChanges,
+	apOfGroupChanges *openflow_13.FlowGroupChanges, apFlowMetaData *voltha.FlowMetadata) error {
+
+	//Remove flows
+	if apOfFlowChanges.ToRemove != nil {
+		for _, flowItem := range apOfFlowChanges.ToRemove.Items {
+			logger.Debugw("incremental flow item remove", log.Fields{"deviceId": dh.deviceID,
+				"Item": flowItem})
+		}
+	}
+	if apOfFlowChanges.ToAdd != nil {
+		for _, flowItem := range apOfFlowChanges.ToAdd.Items {
+			if flowItem.GetCookie() == 0 {
+				logger.Debugw("incremental flow add - no cookie - ignore", log.Fields{
+					"deviceId": dh.deviceID})
+				continue
+			}
+			flowInPort := flow.GetInPort(flowItem)
+			if flowInPort == uint32(of.OfpPortNo_OFPP_INVALID) {
+				logger.Errorw("flow inPort invalid", log.Fields{"deviceID": dh.deviceID})
+				return errors.New("flow inPort invalid")
+			} else if flowInPort == dh.ponPortNumber {
+				//this is some downstream flow
+				logger.Debugw("incremental flow ignore downstream", log.Fields{
+					"deviceId": dh.deviceID, "inPort": flowInPort})
+				continue
+			} else {
+				// this is the relevant upstream flow
+				var loUniPort *OnuUniPort
+				if uniPort, exist := dh.uniEntityMap[flowInPort]; exist {
+					loUniPort = uniPort
+				} else {
+					logger.Errorw("flow inPort not found in UniPorts",
+						log.Fields{"deviceID": dh.deviceID, "inPort": flowInPort})
+					return fmt.Errorf("flow-parameter inPort %d not found in internal UniPorts", flowInPort)
+				}
+				flowOutPort := flow.GetOutPort(flowItem)
+				logger.Debugw("incremental flow-add port indications", log.Fields{
+					"deviceId": dh.deviceID, "inPort": flowInPort, "outPort": flowOutPort,
+					"uniPortName": loUniPort.name})
+				err := dh.addFlowItemToUniPort(flowItem, loUniPort)
+				//abort processing in error case
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
 //DisableDevice locks the ONU and its UNI/VEIP ports (admin lock via OMCI)
 // TODO!!! Clarify usage of this method, it is for sure not used within ONOS (OLT) device disable
 //         maybe it is obsolete by now
@@ -509,7 +567,7 @@
 		var wg sync.WaitGroup
 		wg.Add(1) // for the 1 go routines to finish
 		// attention: deadline completion check and wg.Done is to be done in both routines
-		go dh.pOnuTP.configureUniTp(dctx, uniData.PersUniId, uniData.PersTpPath, &wg)
+		go dh.pOnuTP.configureUniTp(dctx, uint8(uniData.PersUniId), uniData.PersTpPath, &wg)
 		//the wait.. function is responsible for tpProcMutex.Unlock()
 		dh.pOnuTP.waitForTpCompletion(cancel, &wg) //wait for background process to finish and collect their result
 		return
@@ -1080,6 +1138,18 @@
 				if dh.pOnuTP.pAniConfigFsm != nil {
 					dh.pOnuTP.pAniConfigFsm.pAdaptFsm.pFsm.Event(aniEvReset)
 				}
+				for _, uniPort := range dh.uniEntityMap {
+					//reset the TechProfileConfig Done state for all (active) UNI's
+					dh.pOnuTP.setConfigDone(uniPort.uniId, false)
+					// reset tjhe possibly existing VlanConfigFsm
+					if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[uniPort.uniId]; exist {
+						//VlanFilterFsm exists and was already started
+						pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+						if pVlanFilterStatemachine != nil {
+							pVlanFilterStatemachine.Event(vlanEvReset)
+						}
+					}
+				}
 			}
 			//TODO!!! care about PM/Alarm processing once started
 		}
@@ -1275,8 +1345,6 @@
 	case OmciAniConfigDone:
 		{
 			logger.Debugw("OmciAniConfigDone event received", log.Fields{"device-id": dh.deviceID})
-			//TODO!: it might be needed to check some 'cached' pending flow configuration (vlan setting)
-			//  - to consider with outstanding flow implementation
 			// attention: the device reason update is done based on ONU-UNI-Port related activity
 			//  - which may cause some inconsistency
 			if dh.deviceReason != "tech-profile-config-download-success" {
@@ -1298,6 +1366,28 @@
 				dh.deviceReason = "tech-profile-config-download-success"
 			}
 		}
+	case OmciVlanFilterDone:
+		{
+			logger.Debugw("OmciVlanFilterDone event received",
+				log.Fields{"device-id": dh.deviceID})
+			// attention: the device reason update is done based on ONU-UNI-Port related activity
+			//  - which may cause some inconsistency
+			//			yield self.core_proxy.device_reason_update(self.device_id, 'omci-flows-pushed')
+
+			if dh.deviceReason != "omci-flows-pushed" {
+				// which may be the case from some previous actvity on another UNI Port of the ONU
+				// or even some previous flow add activity on the same port
+				if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "omci-flows-pushed"); err != nil {
+					logger.Errorw("error-DeviceReasonUpdate to 'omci-flows-pushed'",
+						log.Fields{"device-id": dh.deviceID, "error": err})
+				} else {
+					logger.Infow("updated dev reason to ''omci-flows-pushed'",
+						log.Fields{"device-id": dh.deviceID})
+				}
+				//set internal state anyway - as it was done
+				dh.deviceReason = "omci-flows-pushed"
+			}
+		}
 	default:
 		{
 			logger.Warnw("unhandled-device-event", log.Fields{"device-id": dh.deviceID, "event": dev_Event})
@@ -1415,7 +1505,7 @@
 		log.Fields{"device-id": a_deviceID, "with-EventName": de.DeviceEventName})
 }
 
-// createUniLockFsm initialises and runs the UniLock FSM to transfer teh OMCi related commands for port lock/unlock
+// createUniLockFsm initialises and runs the UniLock FSM to transfer the OMCI related commands for port lock/unlock
 func (dh *DeviceHandler) createUniLockFsm(aAdminState bool, devEvent OnuDeviceEvent) {
 	chLSFsm := make(chan Message, 2048)
 	var sFsmName string
@@ -1503,3 +1593,257 @@
 
 	return kvbackend
 }
+
+//addFlowItemToUniPort parses the actual flow item to add it to the UniPort
+func (dh *DeviceHandler) addFlowItemToUniPort(apFlowItem *ofp.OfpFlowStats, apUniPort *OnuUniPort) error {
+	var loSetVlan uint16 = uint16(of.OfpVlanId_OFPVID_NONE)      //noValidEntry
+	var loMatchVlan uint16 = uint16(of.OfpVlanId_OFPVID_PRESENT) //reserved VLANID entry
+	var loAddPcp, loSetPcp uint8
+	/* the TechProfileId is part of the flow Metadata - compare also comment within
+	 * OLT-Adapter:openolt_flowmgr.go
+	 *     Metadata 8 bytes:
+	 *	   Most Significant 2 Bytes = Inner VLAN
+	 *	   Next 2 Bytes = Tech Profile ID(TPID)
+	 *	   Least Significant 4 Bytes = Port ID
+	 *     Flow Metadata carries Tech-Profile (TP) ID and is mandatory in all
+	 *     subscriber related flows.
+	 */
+
+	metadata := flow.GetMetadataFromWriteMetadataAction(apFlowItem)
+	if metadata == 0 {
+		logger.Debugw("FlowAdd invalid metadata - abort",
+			log.Fields{"device-id": dh.deviceID})
+		return errors.New("FlowAdd invalid metadata")
+	}
+	loTpID := flow.GetTechProfileIDFromWriteMetaData(metadata)
+	logger.Debugw("FlowAdd TechProfileId", log.Fields{"device-id": dh.deviceID, "TP-Id": loTpID})
+	for _, field := range flow.GetOfbFields(apFlowItem) {
+		switch field.Type {
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_ETH_TYPE:
+			{
+				logger.Debugw("FlowAdd type EthType", log.Fields{"device-id": dh.deviceID,
+					"EthType": strconv.FormatInt(int64(field.GetEthType()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IP_PROTO:
+			{
+				loIPProto := field.GetIpProto()
+				logger.Debugw("FlowAdd type IpProto", log.Fields{"device-id": dh.deviceID,
+					"IpProto": strconv.FormatInt(int64(loIPProto), 16)})
+				if loIPProto == 2 {
+					// some workaround for TT workflow at proto == 2 (IGMP trap) -> ignore the flow
+					// avoids installing invalid EVTOCD rule
+					logger.Debugw("FlowAdd type IpProto 2: TT workaround: ignore flow",
+						log.Fields{"device-id": dh.deviceID,
+							"IpProto": strconv.FormatInt(int64(loIPProto), 16)})
+					return nil
+				}
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
+			{
+				loMatchVlan = uint16(field.GetVlanVid())
+				loMatchVlanMask := uint16(field.GetVlanVidMask())
+				if !(loMatchVlan == uint16(of.OfpVlanId_OFPVID_PRESENT) &&
+					loMatchVlanMask == uint16(of.OfpVlanId_OFPVID_PRESENT)) {
+					loMatchVlan = loMatchVlan & 0xFFF // not transparent: copy only ID bits
+				}
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"VID": strconv.FormatInt(int64(loMatchVlan), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
+			{
+				loAddPcp = uint8(field.GetVlanPcp())
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"PCP": loAddPcp})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_UDP_DST:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"UDP-DST": strconv.FormatInt(int64(field.GetUdpDst()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"UDP-SRC": strconv.FormatInt(int64(field.GetUdpSrc()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"IPv4-DST": field.GetIpv4Dst()})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_SRC:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"IPv4-SRC": field.GetIpv4Src()})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_METADATA:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"Metadata": field.GetTableMetadata()})
+			}
+			/*
+				default:
+					{
+						//all other entires ignored
+					}
+			*/
+		}
+	} //for all OfbFields
+
+	for _, action := range flow.GetActions(apFlowItem) {
+		switch action.Type {
+		/* not used:
+		case of.OfpActionType_OFPAT_OUTPUT:
+			{
+				logger.Debugw("FlowAdd action type", log.Fields{"device-id": dh.deviceID,
+					"Output": action.GetOutput()})
+			}
+		*/
+		case of.OfpActionType_OFPAT_PUSH_VLAN:
+			{
+				logger.Debugw("FlowAdd action type", log.Fields{"device-id": dh.deviceID,
+					"PushEthType": strconv.FormatInt(int64(action.GetPush().Ethertype), 16)})
+			}
+		case of.OfpActionType_OFPAT_SET_FIELD:
+			{
+				pActionSetField := action.GetSetField()
+				if pActionSetField.Field.OxmClass != of.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
+					logger.Warnw("FlowAdd action SetField invalid OxmClass (ignored)", log.Fields{"device-id": dh.deviceID,
+						"OxcmClass": pActionSetField.Field.OxmClass})
+				}
+				if pActionSetField.Field.GetOfbField().Type == of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID {
+					loSetVlan = uint16(pActionSetField.Field.GetOfbField().GetVlanVid())
+					logger.Debugw("FlowAdd Set VLAN from SetField action", log.Fields{"device-id": dh.deviceID,
+						"SetVlan": strconv.FormatInt(int64(loSetVlan), 16)})
+				} else if pActionSetField.Field.GetOfbField().Type == of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP {
+					loSetPcp = uint8(pActionSetField.Field.GetOfbField().GetVlanPcp())
+					logger.Debugw("FlowAdd Set PCP from SetField action", log.Fields{"device-id": dh.deviceID,
+						"SetPcp": loSetPcp})
+				} else {
+					logger.Warnw("FlowAdd action SetField invalid FieldType", log.Fields{"device-id": dh.deviceID,
+						"Type": pActionSetField.Field.GetOfbField().Type})
+				}
+			}
+			/*
+				default:
+					{
+						//all other entires ignored
+					}
+			*/
+		}
+	} //for all Actions
+
+	if loSetVlan == uint16(of.OfpVlanId_OFPVID_NONE) && loMatchVlan != uint16(of.OfpVlanId_OFPVID_PRESENT) {
+		logger.Errorw("FlowAdd aborted - SetVlanId undefined, but MatchVid set", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo,
+			"set_vid":   strconv.FormatInt(int64(loSetVlan), 16),
+			"match_vid": strconv.FormatInt(int64(loMatchVlan), 16)})
+		//TODO!!: Use DeviceId within the error response to rwCore
+		//  likewise also in other error response cases to calling components as requested in [VOL-3458]
+		return errors.New("FlowAdd Set/Match VlanId inconsistent")
+	}
+	if loSetVlan == uint16(of.OfpVlanId_OFPVID_NONE) && loMatchVlan == uint16(of.OfpVlanId_OFPVID_PRESENT) {
+		logger.Debugw("FlowAdd vlan-any/copy", log.Fields{"device-id": dh.deviceID})
+		loSetVlan = loMatchVlan //both 'transparent' (copy any)
+	} else {
+		//looks like OMCI value 4097 (copyFromOuter - for Uni double tagged) is not supported here
+		if loSetVlan != uint16(of.OfpVlanId_OFPVID_PRESENT) {
+			// not set to transparent
+			loSetVlan &= 0x0FFF //mask VID bits as prerequiste for vlanConfigFsm
+		}
+		logger.Debugw("FlowAdd vlan-set", log.Fields{"device-id": dh.deviceID})
+	}
+	//TODO!!: further FlowAdd requests may be valid even in case the FSM is already running,
+	//  e.g. for multi-step flow configuration, error treatment must be redefined in this context as requested in [VOL-3441]
+	if _, exist := dh.UniVlanConfigFsmMap[apUniPort.uniId]; exist {
+		logger.Errorw("FlowAdd aborted - FSM already running", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo})
+		return errors.New("FlowAdd FSM already running")
+	}
+	return dh.createVlanFilterFsm(apUniPort,
+		loTpID, loMatchVlan, loSetVlan, loSetPcp, OmciVlanFilterDone)
+}
+
+// createVlanFilterFsm initialises and runs the VlanFilter FSM to transfer OMCI related VLAN config
+func (dh *DeviceHandler) createVlanFilterFsm(apUniPort *OnuUniPort,
+	aTpID uint16, aMatchVlan uint16, aSetVlan uint16, aSetPcp uint8, aDevEvent OnuDeviceEvent) error {
+	chVlanFilterFsm := make(chan Message, 2048)
+
+	pDevEntry := dh.GetOnuDeviceEntry(true)
+	if pDevEntry == nil {
+		logger.Errorw("No valid OnuDevice -aborting", log.Fields{"device-id": dh.deviceID})
+		return fmt.Errorf("No valid OnuDevice for device-id %x - aborting", dh.deviceID)
+	}
+
+	pVlanFilterFsm := NewUniVlanConfigFsm(dh, pDevEntry.PDevOmciCC, apUniPort, dh.pOnuTP,
+		pDevEntry.pOnuDB, aTpID, aDevEvent, "UniVlanConfigFsm", dh.deviceID, chVlanFilterFsm,
+		dh.pOpenOnuAc.AcceptIncrementalEvto, aMatchVlan, aSetVlan, aSetPcp)
+	if pVlanFilterFsm != nil {
+		dh.UniVlanConfigFsmMap[apUniPort.uniId] = pVlanFilterFsm
+		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+		if pVlanFilterStatemachine != nil {
+			if pVlanFilterStatemachine.Is(vlanStDisabled) {
+				if err := pVlanFilterStatemachine.Event(vlanEvStart); err != nil {
+					logger.Warnw("UniVlanConfigFsm: can't start", log.Fields{"err": err})
+					return fmt.Errorf("Can't start UniVlanConfigFsm for device-id %x", dh.deviceID)
+				} else {
+					/***** UniVlanConfigFsm started */
+					logger.Debugw("UniVlanConfigFsm started", log.Fields{
+						"state": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID,
+						"UniPort": apUniPort.portNo})
+				}
+			} else {
+				logger.Warnw("wrong state of UniVlanConfigFsm - want: disabled", log.Fields{
+					"have": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID})
+				return fmt.Errorf("UniVlanConfigFsm not in expected disabled state for device-id %x", dh.deviceID)
+			}
+		} else {
+			logger.Errorw("UniVlanConfigFsm StateMachine invalid - cannot be executed!!", log.Fields{
+				"device-id": dh.deviceID})
+			return fmt.Errorf("UniVlanConfigFsm invalid for device-id %x", dh.deviceID)
+		}
+	} else {
+		logger.Errorw("UniVlanConfigFsm could not be created - abort!!", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo})
+		return fmt.Errorf("UniVlanConfigFsm could not be created for device-id %x", dh.deviceID)
+	}
+	return nil
+}
+
+//verifyUniVlanConfigRequest checks on existence of flow configuration and starts it accordingly
+func (dh *DeviceHandler) verifyUniVlanConfigRequest(apUniPort *OnuUniPort) {
+	//TODO!! verify and start pending flow configuration
+	//some pending config request my exist in case the UniVlanConfig FSM was already started - with internal data -
+	//but execution was set to 'on hold' as first the TechProfile config had to be applied
+	if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[apUniPort.uniId]; exist {
+		//VlanFilterFsm exists and was already started (assumed to wait for TechProfile execution here)
+		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+		if pVlanFilterStatemachine != nil {
+			if pVlanFilterStatemachine.Is(vlanStWaitingTechProf) {
+				if err := pVlanFilterStatemachine.Event(vlanEvContinueConfig); err != nil {
+					logger.Warnw("UniVlanConfigFsm: can't continue processing", log.Fields{"err": err})
+				} else {
+					/***** UniVlanConfigFsm continued */
+					logger.Debugw("UniVlanConfigFsm continued", log.Fields{
+						"state": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID,
+						"UniPort": apUniPort.portNo})
+				}
+			} else {
+				logger.Debugw("no state of UniVlanConfigFsm to be continued", log.Fields{
+					"have": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID})
+			}
+		} else {
+			logger.Debugw("UniVlanConfigFsm StateMachine does not exist, no flow processing", log.Fields{
+				"device-id": dh.deviceID})
+		}
+
+	} // else: nothing to do
+}
+
+//RemoveVlanFilterFsm deletes the stored pointer to the VlanConfigFsm
+// intention is to provide this method to be called from VlanConfigFsm itself, when resources (and methods!) are cleaned up
+func (dh *DeviceHandler) RemoveVlanFilterFsm(apUniPort *OnuUniPort) {
+	logger.Debugw("remove UniVlanConfigFsm StateMachine", log.Fields{
+		"device-id": dh.deviceID, "uniPort": apUniPort.portNo})
+	//save to do, even if entry dows not exist
+	delete(dh.UniVlanConfigFsmMap, apUniPort.uniId)
+}
diff --git a/internal/pkg/onuadaptercore/omci_ani_config.go b/internal/pkg/onuadaptercore/omci_ani_config.go
index f724e0f..b152818 100644
--- a/internal/pkg/onuadaptercore/omci_ani_config.go
+++ b/internal/pkg/onuadaptercore/omci_ani_config.go
@@ -37,13 +37,13 @@
 	// events of config PON ANI port FSM
 	aniEvStart           = "uniEvStart"
 	aniEvStartConfig     = "aniEvStartConfig"
-	aniEvRxDot1pmapCresp = "aniEvRxDot1pmapCresp"
+	aniEvRxDot1pmapCResp = "aniEvRxDot1pmapCResp"
 	aniEvRxMbpcdResp     = "aniEvRxMbpcdResp"
 	aniEvRxTcontsResp    = "aniEvRxTcontsResp"
 	aniEvRxGemntcpsResp  = "aniEvRxGemntcpsResp"
 	aniEvRxGemiwsResp    = "aniEvRxGemiwsResp"
 	aniEvRxPrioqsResp    = "aniEvRxPrioqsResp"
-	aniEvRxDot1pmapSresp = "aniEvRxDot1pmapSresp"
+	aniEvRxDot1pmapSResp = "aniEvRxDot1pmapSResp"
 	aniEvTimeoutSimple   = "aniEvTimeoutSimple"
 	aniEvTimeoutMids     = "aniEvTimeoutMids"
 	aniEvReset           = "aniEvReset"
@@ -124,7 +124,7 @@
 
 			//Note: .1p-Mapper and MBPCD might also have multi instances (per T-Cont) - by now only one 1 T-Cont considered!
 			{Name: aniEvStartConfig, Src: []string{aniStStarting}, Dst: aniStCreatingDot1PMapper},
-			{Name: aniEvRxDot1pmapCresp, Src: []string{aniStCreatingDot1PMapper}, Dst: aniStCreatingMBPCD},
+			{Name: aniEvRxDot1pmapCResp, Src: []string{aniStCreatingDot1PMapper}, Dst: aniStCreatingMBPCD},
 			{Name: aniEvRxMbpcdResp, Src: []string{aniStCreatingMBPCD}, Dst: aniStSettingTconts},
 			{Name: aniEvRxTcontsResp, Src: []string{aniStSettingTconts}, Dst: aniStCreatingGemNCTPs},
 			// the creatingGemNCTPs state is used for multi ME config if required for all configured/available GemPorts
@@ -133,7 +133,7 @@
 			{Name: aniEvRxGemiwsResp, Src: []string{aniStCreatingGemIWs}, Dst: aniStSettingPQs},
 			// the settingPQs state is used for multi ME config if required for all configured/available upstream PriorityQueues
 			{Name: aniEvRxPrioqsResp, Src: []string{aniStSettingPQs}, Dst: aniStSettingDot1PMapper},
-			{Name: aniEvRxDot1pmapSresp, Src: []string{aniStSettingDot1PMapper}, Dst: aniStConfigDone},
+			{Name: aniEvRxDot1pmapSResp, Src: []string{aniStSettingDot1PMapper}, Dst: aniStConfigDone},
 
 			{Name: aniEvTimeoutSimple, Src: []string{
 				aniStCreatingDot1PMapper, aniStCreatingMBPCD, aniStSettingTconts, aniStSettingDot1PMapper}, Dst: aniStStarting},
@@ -199,7 +199,7 @@
 	oFsm.gemPortAttribsSlice = nil
 
 	// start go routine for processing of LockState messages
-	go oFsm.ProcessOmciAniMessages()
+	go oFsm.processOmciAniMessages()
 
 	//let the state machine run forward from here directly
 	pConfigAniStateAFsm := oFsm.pAdaptFsm
@@ -226,10 +226,10 @@
 				} else {
 					logger.Warnw("No TCont instances found", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
 				}
-				oFsm.alloc0ID = (*(oFsm.pUniTechProf.mapPonAniConfig[uint32(oFsm.pOnuUniPort.uniId)]))[0].tcontParams.allocID
+				oFsm.alloc0ID = (*(oFsm.pUniTechProf.mapPonAniConfig[oFsm.pOnuUniPort.uniId]))[0].tcontParams.allocID
 				loGemPortAttribs := ponAniGemPortAttribs{}
 				//for all TechProfile set GemIndices
-				for _, gemEntry := range (*(oFsm.pUniTechProf.mapPonAniConfig[uint32(oFsm.pOnuUniPort.uniId)]))[0].mapGemPortParams {
+				for _, gemEntry := range (*(oFsm.pUniTechProf.mapPonAniConfig[oFsm.pOnuUniPort.uniId]))[0].mapGemPortParams {
 					//collect all GemConfigData in a seperate Fsm related slice (needed also to avoid mix-up with unsorted mapPonAniConfig)
 
 					if queueInstKeys := oFsm.pOnuDB.GetSortedInstKeys(me.PriorityQueueClassID); len(queueInstKeys) > 0 {
@@ -272,7 +272,7 @@
 										logger.Warnw("Could not convert attribute value", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
 									}
 								} else {
-									logger.Warnw("'relatedPort' not found in meAttributes:", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+									logger.Warnw("'RelatedPort' not found in meAttributes:", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
 								}
 							} else {
 								logger.Warnw("No attributes available in DB:", log.Fields{"meClassID": me.PriorityQueueClassID,
@@ -356,7 +356,6 @@
 }
 
 func (oFsm *UniPonAniConfigFsm) enterCreatingGemNCTPs(e *fsm.Event) {
-	//TODO!! this is just for the first GemPort right now - needs update
 	logger.Debugw("UniPonAniConfigFsm - start creating GemNWCtp loop", log.Fields{
 		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
 	go oFsm.performCreatingGemNCTPs()
@@ -530,6 +529,10 @@
 		oFsm.pOmciCC.pBaseDeviceHandler.DeviceProcStatusUpdate(oFsm.requestEvent)
 		oFsm.aniConfigCompleted = false
 	}
+	//store that the UNI related techProfile processing is done for the given Profile and Uni
+	oFsm.pUniTechProf.setConfigDone(oFsm.pOnuUniPort.uniId, true)
+	//if techProfile processing is done it must be checked, if some prior/parallel flow configuration is pending
+	go oFsm.pOmciCC.pBaseDeviceHandler.verifyUniVlanConfigRequest(oFsm.pOnuUniPort)
 
 	if oFsm.chanSet {
 		// indicate processing done to the caller
@@ -541,7 +544,7 @@
 
 }
 
-func (oFsm *UniPonAniConfigFsm) ProcessOmciAniMessages( /*ctx context.Context*/ ) {
+func (oFsm *UniPonAniConfigFsm) processOmciAniMessages( /*ctx context.Context*/ ) {
 	logger.Debugw("Start UniPonAniConfigFsm Msg processing", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
 loop:
 	for {
@@ -603,16 +606,12 @@
 			}
 			if msgObj.EntityClass == oFsm.pOmciCC.pLastTxMeInstance.GetClassID() &&
 				msgObj.EntityInstance == oFsm.pOmciCC.pLastTxMeInstance.GetEntityID() {
-				//store the created ME into DB //TODO??? obviously the Python code does not store the config ...
-				// if, then something like:
-				//oFsm.pOnuDB.StoreMe(msgObj)
-
 				// maybe we can use just the same eventName for different state transitions like "forward"
 				//   - might be checked, but so far I go for sure and have to inspect the concrete state events ...
 				switch oFsm.pOmciCC.pLastTxMeInstance.GetName() {
 				case "Ieee8021PMapperServiceProfile":
 					{ // let the FSM proceed ...
-						oFsm.pAdaptFsm.pFsm.Event(aniEvRxDot1pmapCresp)
+						oFsm.pAdaptFsm.pFsm.Event(aniEvRxDot1pmapCResp)
 					}
 				case "MacBridgePortConfigurationData":
 					{ // let the FSM proceed ...
@@ -660,7 +659,7 @@
 					}
 				case "Ieee8021PMapperServiceProfile":
 					{ // let the FSM proceed ...
-						oFsm.pAdaptFsm.pFsm.Event(aniEvRxDot1pmapSresp)
+						oFsm.pAdaptFsm.pFsm.Event(aniEvRxDot1pmapSResp)
 					}
 				}
 			}
diff --git a/internal/pkg/onuadaptercore/omci_cc.go b/internal/pkg/onuadaptercore/omci_cc.go
index 5ee61f1..23fd506 100644
--- a/internal/pkg/onuadaptercore/omci_cc.go
+++ b/internal/pkg/onuadaptercore/omci_cc.go
@@ -25,6 +25,7 @@
 	"errors"
 	"strconv"
 	"sync"
+
 	//"time"
 
 	"github.com/google/gopacket"
@@ -395,6 +396,7 @@
 	// block parallel omci send requests at least until SendIAP is 'committed'
 	// that should be feasible for an onu instance as on OMCI anyway window size 1 is assumed
 	oo.mutexTxQueue.Lock()
+	defer oo.mutexTxQueue.Unlock()
 	for oo.txQueue.Len() > 0 {
 		queueElement := oo.txQueue.Front() // First element
 		omciTxRequest := queueElement.Value.(omciTransferStructure)
@@ -476,7 +478,6 @@
 		}
 		oo.txQueue.Remove(queueElement) // Dequeue
 	}
-	oo.mutexTxQueue.Unlock()
 	return nil
 }
 
@@ -1391,3 +1392,90 @@
 		"Err": omciErr.GetError(), "device-id": oo.deviceID})
 	return nil
 }
+
+func (oo *OmciCC) sendCreateVtfdVar(ctx context.Context, timeout int, highPrio bool,
+	rxChan chan Message, params ...me.ParamData) *me.ManagedEntity {
+	tid := oo.GetNextTid(highPrio)
+	logger.Debugw("send VTFD-Create-msg:", log.Fields{"device-id": oo.deviceID,
+		"SequNo": strconv.FormatInt(int64(tid), 16),
+		"InstId": strconv.FormatInt(int64(params[0].EntityID), 16)})
+
+	meInstance, omciErr := me.NewVlanTaggingFilterData(params[0])
+	if omciErr.GetError() == nil {
+		//all SetByCreate Parameters (assumed to be) set here, for optimisation no 'AddDefaults'
+		omciLayer, msgLayer, err := omci.EncodeFrame(meInstance, omci.CreateRequestType,
+			omci.TransactionID(tid))
+		if err != nil {
+			logger.Errorw("Cannot encode VTFD for create", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			//TODO!!: refactoring improvement requested, here as an example for [VOL-3457]:
+			//  return (dual format) error code that can be used at caller for immediate error treatment
+			//  (relevant to all used sendXX() methods and their error conditions)
+			return nil
+		}
+
+		pkt, err := serializeOmciLayer(omciLayer, msgLayer)
+		if err != nil {
+			logger.Errorw("Cannot serialize VTFD create", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			return nil
+		}
+
+		omciRxCallbackPair := CallbackPair{
+			cbKey:   tid,
+			cbEntry: CallbackPairEntry{rxChan, oo.receiveOmciResponse},
+		}
+		err = oo.Send(ctx, pkt, timeout, 0, highPrio, omciRxCallbackPair)
+		if err != nil {
+			logger.Errorw("Cannot send VTFD create", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			return nil
+		}
+		logger.Debug("send VTFD-Create-msg done")
+		return meInstance
+	}
+	logger.Errorw("Cannot generate VTFD Instance", log.Fields{
+		"Err": omciErr.GetError(), "device-id": oo.deviceID})
+	return nil
+}
+
+func (oo *OmciCC) sendSetEvtocdVar(ctx context.Context, timeout int, highPrio bool,
+	rxChan chan Message, params ...me.ParamData) *me.ManagedEntity {
+	tid := oo.GetNextTid(highPrio)
+	logger.Debugw("send EVTOCD-Set-msg:", log.Fields{"device-id": oo.deviceID,
+		"SequNo": strconv.FormatInt(int64(tid), 16),
+		"InstId": strconv.FormatInt(int64(params[0].EntityID), 16)})
+
+	meInstance, omciErr := me.NewExtendedVlanTaggingOperationConfigurationData(params[0])
+	if omciErr.GetError() == nil {
+		omciLayer, msgLayer, err := omci.EncodeFrame(meInstance, omci.SetRequestType, omci.TransactionID(tid))
+		if err != nil {
+			logger.Errorw("Cannot encode EVTOCD for set", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			return nil
+		}
+
+		pkt, err := serializeOmciLayer(omciLayer, msgLayer)
+		if err != nil {
+			logger.Errorw("Cannot serialize EVTOCD set", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			return nil
+		}
+
+		omciRxCallbackPair := CallbackPair{
+			cbKey:   tid,
+			cbEntry: CallbackPairEntry{rxChan, oo.receiveOmciResponse},
+		}
+		err = oo.Send(ctx, pkt, timeout, 0, highPrio, omciRxCallbackPair)
+		if err != nil {
+			logger.Errorw("Cannot send EVTOCD set", log.Fields{
+				"Err": err, "device-id": oo.deviceID})
+			return nil
+		}
+		logger.Debug("send EVTOCD-set msg done")
+		return meInstance
+	}
+	logger.Errorw("Cannot generate EVTOCD Instance", log.Fields{
+		"Err": omciErr.GetError(), "device-id": oo.deviceID})
+	return nil
+}
diff --git a/internal/pkg/onuadaptercore/omci_vlan_config.go b/internal/pkg/onuadaptercore/omci_vlan_config.go
new file mode 100644
index 0000000..f60cd93
--- /dev/null
+++ b/internal/pkg/onuadaptercore/omci_vlan_config.go
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+//Package adaptercoreonu provides the utility for onu devices, flows and statistics
+package adaptercoreonu
+
+import (
+	"context"
+	"encoding/binary"
+	"errors"
+	"strconv"
+	"time"
+
+	"github.com/looplab/fsm"
+	"github.com/opencord/omci-lib-go"
+	me "github.com/opencord/omci-lib-go/generated"
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+	of "github.com/opencord/voltha-protos/v3/go/openflow_13"
+)
+
+const (
+	// internal predefined values
+	cDefaultDownstreamMode = 0
+	cDefaultTpid           = 0x8100
+)
+
+const (
+	// bit mask offsets for EVTOCD VlanTaggingOperationTable related to 32 bits (4 bytes)
+	cFilterPrioOffset      = 28
+	cFilterVidOffset       = 15
+	cFilterTpidOffset      = 12
+	cFilterEtherTypeOffset = 0
+	cTreatTTROffset        = 30
+	cTreatPrioOffset       = 16
+	cTreatVidOffset        = 3
+	cTreatTpidOffset       = 0
+)
+const (
+	// byte offsets for EVTOCD VlanTaggingOperationTable related to overall 16 byte size with slice byte 0 as first Byte (MSB)
+	cFilterOuterOffset = 0
+	cFilterInnerOffset = 4
+	cTreatOuterOffset  = 8
+	cTreatInnerOffset  = 12
+)
+const (
+	// basic values used within EVTOCD VlanTaggingOperationTable in respect to their bitfields
+	cPrioIgnoreTag        uint32 = 15
+	cPrioDefaultFilter    uint32 = 14
+	cPrioDoNotFilter      uint32 = 8
+	cDoNotFilterVid       uint32 = 4096
+	cDoNotFilterTPID      uint32 = 0
+	cDoNotFilterEtherType uint32 = 0
+	cDoNotAddPrio         uint32 = 15
+	cCopyPrioFromInner    uint32 = 8
+	cDontCarePrio         uint32 = 0
+	cDontCareVid          uint32 = 0
+	cDontCareTpid         uint32 = 0
+	cSetOutputTpidCopyDei uint32 = 4
+)
+
+const (
+	// events of config PON ANI port FSM
+	vlanEvStart          = "vlanEvStart"
+	vlanEvWaitTechProf   = "vlanEvWaitTechProf"
+	vlanEvContinueConfig = "vlanEvContinueConfig"
+	vlanEvStartConfig    = "vlanEvStartConfig"
+	vlanEvRxConfigVtfd   = "vlanEvRxConfigVtfd"
+	vlanEvRxConfigEvtocd = "vlanEvRxConfigEvtocd"
+	vlanEvCleanupConfig  = "vlanEvCleanupConfig"
+	vlanEvRxCleanVtfd    = "vlanEvRxCleanVtfd"
+	vlanEvRxCleanEvtocd  = "vlanEvRxCleanEvtocd"
+	vlanEvTimeoutSimple  = "vlanEvTimeoutSimple"
+	vlanEvTimeoutMids    = "vlanEvTimeoutMids"
+	vlanEvReset          = "vlanEvReset"
+	vlanEvRestart        = "vlanEvRestart"
+)
+const (
+	// states of config PON ANI port FSM
+	vlanStDisabled        = "vlanStDisabled"
+	vlanStStarting        = "vlanStStarting"
+	vlanStWaitingTechProf = "vlanStWaitingTechProf"
+	vlanStConfigVtfd      = "vlanStConfigVtfd"
+	vlanStConfigEvtocd    = "vlanStConfigEvtocd"
+	vlanStConfigDone      = "vlanStConfigDone"
+	vlanStCleanEvtocd     = "vlanStCleanEvtocd"
+	vlanStCleanVtfd       = "vlanStCleanVtfd"
+	vlanStCleanupDone     = "vlanStCleanupDone"
+	vlanStResetting       = "vlanStResetting"
+)
+
+//UniVlanConfigFsm defines the structure for the state machine to config the PON ANI ports of ONU UNI ports via OMCI
+type UniVlanConfigFsm struct {
+	pDeviceHandler              *DeviceHandler
+	pOmciCC                     *OmciCC
+	pOnuUniPort                 *OnuUniPort
+	pUniTechProf                *OnuUniTechProf
+	pOnuDB                      *OnuDeviceDB
+	techProfileID               uint16
+	requestEvent                OnuDeviceEvent
+	omciMIdsResponseReceived    chan bool //seperate channel needed for checking multiInstance OMCI message responses
+	pAdaptFsm                   *AdapterFsm
+	acceptIncrementalEvtoOption bool
+	//use uint32 types for allowing immediate bitshifting
+	matchVid     uint32
+	matchPcp     uint32
+	tagsToRemove uint32
+	setVid       uint32
+	setPcp       uint32
+	vtfdID       uint16
+	evtocdID     uint16
+}
+
+//NewUniVlanConfigFsm is the 'constructor' for the state machine to config the PON ANI ports of ONU UNI ports via OMCI
+func NewUniVlanConfigFsm(apDeviceHandler *DeviceHandler, apDevOmciCC *OmciCC, apUniPort *OnuUniPort, apUniTechProf *OnuUniTechProf,
+	apOnuDB *OnuDeviceDB, aTechProfileID uint16, aRequestEvent OnuDeviceEvent, aName string,
+	aDeviceID string, aCommChannel chan Message,
+	aAcceptIncrementalEvto bool, aMatchVlan uint16, aSetVlan uint16, aSetPcp uint8) *UniVlanConfigFsm {
+	instFsm := &UniVlanConfigFsm{
+		pDeviceHandler:              apDeviceHandler,
+		pOmciCC:                     apDevOmciCC,
+		pOnuUniPort:                 apUniPort,
+		pUniTechProf:                apUniTechProf,
+		pOnuDB:                      apOnuDB,
+		techProfileID:               aTechProfileID,
+		requestEvent:                aRequestEvent,
+		acceptIncrementalEvtoOption: aAcceptIncrementalEvto,
+		matchVid:                    uint32(aMatchVlan),
+		setVid:                      uint32(aSetVlan),
+		setPcp:                      uint32(aSetPcp),
+	}
+	// some automatic adjustments on the filter/treat parameters as not specifically configured/ensured by flow configuration parameters
+	instFsm.tagsToRemove = 1            //one tag to remove as default setting
+	instFsm.matchPcp = cPrioDoNotFilter // do not Filter on prio as default
+	if instFsm.matchVid == uint32(of.OfpVlanId_OFPVID_PRESENT) {
+		// no prio/vid filtering requested
+		instFsm.tagsToRemove = 0          //no tag pop action
+		instFsm.matchPcp = cPrioIgnoreTag // no vlan tag filtering
+		if instFsm.setPcp == cCopyPrioFromInner {
+			//in case of no filtering and configured PrioCopy ensure default prio setting to 0
+			// which is required for stacking of untagged, but obviously also ensures prio setting for prio/singletagged
+			// might collide with NoMatchVid/CopyPrio(/setVid) setting
+			// this was some precondition setting taken over from py adapter ..
+			instFsm.setPcp = 0
+		}
+	}
+
+	instFsm.pAdaptFsm = NewAdapterFsm(aName, aDeviceID, aCommChannel)
+	if instFsm.pAdaptFsm == nil {
+		logger.Errorw("UniVlanConfigFsm's AdapterFsm could not be instantiated!!", log.Fields{
+			"device-id": aDeviceID})
+		return nil
+	}
+
+	instFsm.pAdaptFsm.pFsm = fsm.NewFSM(
+		vlanStDisabled,
+		fsm.Events{
+			{Name: vlanEvStart, Src: []string{vlanStDisabled}, Dst: vlanStStarting},
+			{Name: vlanEvWaitTechProf, Src: []string{vlanStStarting}, Dst: vlanStWaitingTechProf},
+			{Name: vlanEvContinueConfig, Src: []string{vlanStWaitingTechProf}, Dst: vlanStConfigVtfd},
+			{Name: vlanEvStartConfig, Src: []string{vlanStStarting}, Dst: vlanStConfigVtfd},
+			{Name: vlanEvRxConfigVtfd, Src: []string{vlanStConfigVtfd}, Dst: vlanStConfigEvtocd},
+			{Name: vlanEvRxConfigEvtocd, Src: []string{vlanStConfigEvtocd}, Dst: vlanStConfigDone},
+			//TODO:!!! Also define state transitions for cleanup states and timeouts
+			/*
+				{Name: vlanEvTimeoutSimple, Src: []string{
+					vlanStCreatingDot1PMapper, vlanStCreatingMBPCD, vlanStSettingTconts, vlanStSettingDot1PMapper}, Dst: vlanStStarting},
+				{Name: vlanEvTimeoutMids, Src: []string{
+					vlanStCreatingGemNCTPs, vlanStCreatingGemIWs, vlanStSettingPQs}, Dst: vlanStStarting},
+			*/
+			// exceptional treatment for all states except vlanStResetting
+			{Name: vlanEvReset, Src: []string{vlanStStarting, vlanStWaitingTechProf,
+				vlanStConfigVtfd, vlanStConfigEvtocd, vlanStConfigDone,
+				vlanStCleanEvtocd, vlanStCleanVtfd, vlanStCleanupDone},
+				Dst: vlanStResetting},
+			// the only way to get to resource-cleared disabled state again is via "resseting"
+			{Name: vlanEvRestart, Src: []string{vlanStResetting}, Dst: vlanStDisabled},
+		},
+
+		fsm.Callbacks{
+			"enter_state":                   func(e *fsm.Event) { instFsm.pAdaptFsm.logFsmStateChange(e) },
+			("enter_" + vlanStStarting):     func(e *fsm.Event) { instFsm.enterConfigStarting(e) },
+			("enter_" + vlanStConfigVtfd):   func(e *fsm.Event) { instFsm.enterConfigVtfd(e) },
+			("enter_" + vlanStConfigEvtocd): func(e *fsm.Event) { instFsm.enterConfigEvtocd(e) },
+			("enter_" + vlanStConfigDone):   func(e *fsm.Event) { instFsm.enterVlanConfigDone(e) },
+			("enter_" + vlanStCleanVtfd):    func(e *fsm.Event) { instFsm.enterCleanVtfd(e) },
+			("enter_" + vlanStCleanEvtocd):  func(e *fsm.Event) { instFsm.enterCleanEvtocd(e) },
+			("enter_" + vlanStCleanupDone):  func(e *fsm.Event) { instFsm.enterVlanCleanupDone(e) },
+			("enter_" + vlanStResetting):    func(e *fsm.Event) { instFsm.enterResetting(e) },
+			("enter_" + vlanStDisabled):     func(e *fsm.Event) { instFsm.enterDisabled(e) },
+		},
+	)
+	if instFsm.pAdaptFsm.pFsm == nil {
+		logger.Errorw("UniVlanConfigFsm's Base FSM could not be instantiated!!", log.Fields{
+			"device-id": aDeviceID})
+		return nil
+	}
+
+	logger.Infow("UniVlanConfigFsm created", log.Fields{"device-id": aDeviceID,
+		"accIncrEvto": instFsm.acceptIncrementalEvtoOption,
+		"matchVid":    strconv.FormatInt(int64(instFsm.matchVid), 16),
+		"setVid":      strconv.FormatInt(int64(instFsm.setVid), 16),
+		"setPcp":      instFsm.setPcp})
+	return instFsm
+}
+
+func (oFsm *UniVlanConfigFsm) enterConfigStarting(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm start", log.Fields{"in state": e.FSM.Current(),
+		"device-id": oFsm.pAdaptFsm.deviceID})
+
+	// this FSM is not intended for re-start, needs always new creation for a new run
+	oFsm.omciMIdsResponseReceived = make(chan bool)
+	// start go routine for processing of LockState messages
+	go oFsm.processOmciVlanMessages()
+	//let the state machine run forward from here directly
+	pConfigVlanStateAFsm := oFsm.pAdaptFsm
+	if pConfigVlanStateAFsm != nil {
+		// obviously calling some FSM event here directly does not work - so trying to decouple it ...
+		go func(a_pAFsm *AdapterFsm) {
+			if a_pAFsm != nil && a_pAFsm.pFsm != nil {
+				//stick to pythonAdapter numbering scheme
+				oFsm.vtfdID = macBridgePortAniEID + oFsm.pOnuUniPort.entityId + oFsm.techProfileID
+				//cmp also usage in EVTOCDE create in omci_cc
+				oFsm.evtocdID = macBridgeServiceProfileEID + uint16(oFsm.pOnuUniPort.macBpNo)
+
+				if oFsm.pUniTechProf.getTechProfileDone(oFsm.pOnuUniPort.uniId, oFsm.techProfileID) {
+					// let the vlan processing begin
+					a_pAFsm.pFsm.Event(vlanEvStartConfig)
+				} else {
+					// set to waiting for Techprofile
+					a_pAFsm.pFsm.Event(vlanEvWaitTechProf)
+				}
+			}
+		}(pConfigVlanStateAFsm)
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) enterConfigVtfd(e *fsm.Event) {
+	if oFsm.setVid == uint32(of.OfpVlanId_OFPVID_PRESENT) {
+		// meaning transparent setup - no specific VTFD setting required
+		logger.Debugw("UniVlanConfigFsm: no VTFD config required", log.Fields{
+			"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+		// let the FSM proceed ... (from within this state all internal pointers may be expected to be correct)
+		// obviously calling some FSM event here directly does not work - so trying to decouple it ...
+		pConfigVlanStateAFsm := oFsm.pAdaptFsm
+		go func(a_pAFsm *AdapterFsm) {
+			a_pAFsm.pFsm.Event(vlanEvRxConfigVtfd)
+		}(pConfigVlanStateAFsm)
+	} else {
+		logger.Debugw("UniVlanConfigFsm create VTFD", log.Fields{
+			"EntitytId": strconv.FormatInt(int64(oFsm.vtfdID), 16),
+			"in state":  e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+		vlanFilterList := make([]uint16, 12)
+		vlanFilterList[0] = uint16(oFsm.setVid) // setVid is assumed to be masked already by the caller to 12 bit
+		meParams := me.ParamData{
+			EntityID: oFsm.vtfdID,
+			Attributes: me.AttributeValueMap{
+				"VlanFilterList":   vlanFilterList,
+				"ForwardOperation": uint8(0x10), //VID investigation
+				"NumberOfEntries":  uint8(1),
+			},
+		}
+		meInstance := oFsm.pOmciCC.sendCreateVtfdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+			oFsm.pAdaptFsm.commChan, meParams)
+		//accept also nil as (error) return value for writing to LastTx
+		//  - this avoids misinterpretation of new received OMCI messages
+		//TODO!!: refactoring improvement requested, here as an example for [VOL-3457]:
+		//  send shall return (dual format) error code that can be used here for immediate error treatment
+		//  (relevant to all used sendXX() methods in this (and other) FSM's)
+		oFsm.pOmciCC.pLastTxMeInstance = meInstance
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) enterConfigEvtocd(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm - start config EVTOCD loop", log.Fields{
+		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+	go oFsm.performConfigEvtocdEntries()
+}
+
+func (oFsm *UniVlanConfigFsm) enterVlanConfigDone(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm - VLAN config done: send dh event notification", log.Fields{
+		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+	if oFsm.pDeviceHandler != nil {
+		oFsm.pDeviceHandler.DeviceProcStatusUpdate(oFsm.requestEvent)
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) enterCleanVtfd(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm Tx Delete::VTFD", log.Fields{
+		/*"EntitytId": strconv.FormatInt(int64(oFsm.mapperSP0ID), 16),*/
+		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+}
+
+func (oFsm *UniVlanConfigFsm) enterCleanEvtocd(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm  cleanup EVTOCD", log.Fields{
+		/*"EntitytId": strconv.FormatInt(int64(oFsm.macBPCD0ID), 16),
+		"TPPtr":     strconv.FormatInt(int64(oFsm.mapperSP0ID), 16),*/
+		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+}
+
+func (oFsm *UniVlanConfigFsm) enterVlanCleanupDone(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm - VLAN cleanup done", log.Fields{
+		"in state": e.FSM.Current(), "device-id": oFsm.pAdaptFsm.deviceID})
+
+	//let's reset the state machine in order to release all resources now
+	pConfigVlanStateAFsm := oFsm.pAdaptFsm
+	if pConfigVlanStateAFsm != nil {
+		// obviously calling some FSM event here directly does not work - so trying to decouple it ...
+		go func(a_pAFsm *AdapterFsm) {
+			if a_pAFsm != nil && a_pAFsm.pFsm != nil {
+				a_pAFsm.pFsm.Event(vlanEvReset)
+			}
+		}(pConfigVlanStateAFsm)
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) enterResetting(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm resetting", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+
+	pConfigVlanStateAFsm := oFsm.pAdaptFsm
+	if pConfigVlanStateAFsm != nil {
+		// abort running message processing
+		fsmAbortMsg := Message{
+			Type: TestMsg,
+			Data: TestMessage{
+				TestMessageVal: AbortMessageProcessing,
+			},
+		}
+		pConfigVlanStateAFsm.commChan <- fsmAbortMsg
+
+		//try to restart the FSM to 'disabled', decouple event transfer
+		go func(a_pAFsm *AdapterFsm) {
+			if a_pAFsm != nil && a_pAFsm.pFsm != nil {
+				a_pAFsm.pFsm.Event(vlanEvRestart)
+			}
+		}(pConfigVlanStateAFsm)
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) enterDisabled(e *fsm.Event) {
+	logger.Debugw("UniVlanConfigFsm enters disabled state", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+	if oFsm.pDeviceHandler != nil {
+		//request removal of 'reference' in the Handler (completely clear the FSM)
+		go oFsm.pDeviceHandler.RemoveVlanFilterFsm(oFsm.pOnuUniPort)
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) processOmciVlanMessages() { //ctx context.Context?
+	logger.Debugw("Start UniVlanConfigFsm Msg processing", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
+loop:
+	for {
+		select {
+		// case <-ctx.Done():
+		// 	logger.Info("MibSync Msg", log.Fields{"Message handling canceled via context for device-id": oFsm.pAdaptFsm.deviceID})
+		// 	break loop
+		case message, ok := <-oFsm.pAdaptFsm.commChan:
+			if !ok {
+				logger.Info("UniVlanConfigFsm Rx Msg - could not read from channel", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+				// but then we have to ensure a restart of the FSM as well - as exceptional procedure
+				oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+				break loop
+			}
+			logger.Debugw("UniVlanConfigFsm Rx Msg", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+
+			switch message.Type {
+			case TestMsg:
+				msg, _ := message.Data.(TestMessage)
+				if msg.TestMessageVal == AbortMessageProcessing {
+					logger.Infow("UniVlanConfigFsm abort ProcessMsg", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
+					break loop
+				}
+				logger.Warnw("UniVlanConfigFsm unknown TestMessage", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID, "MessageVal": msg.TestMessageVal})
+			case OMCI:
+				msg, _ := message.Data.(OmciMessage)
+				oFsm.handleOmciVlanConfigMessage(msg)
+			default:
+				logger.Warn("UniVlanConfigFsm Rx unknown message", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID,
+					"message.Type": message.Type})
+			}
+		}
+	}
+	logger.Infow("End UniVlanConfigFsm Msg processing", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+}
+
+func (oFsm *UniVlanConfigFsm) handleOmciVlanConfigMessage(msg OmciMessage) {
+	logger.Debugw("Rx OMCI UniVlanConfigFsm Msg", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID,
+		"msgType": msg.OmciMsg.MessageType})
+
+	switch msg.OmciMsg.MessageType {
+	case omci.CreateResponseType:
+		{
+			msgLayer := (*msg.OmciPacket).Layer(omci.LayerTypeCreateResponse)
+			if msgLayer == nil {
+				logger.Error("Omci Msg layer could not be detected for CreateResponse")
+				return
+			}
+			msgObj, msgOk := msgLayer.(*omci.CreateResponse)
+			if !msgOk {
+				logger.Error("Omci Msg layer could not be assigned for CreateResponse")
+				return
+			}
+			logger.Debugw("CreateResponse Data", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID, "data-fields": msgObj})
+			if msgObj.Result != me.Success {
+				logger.Errorw("Omci CreateResponse Error - later: drive FSM to abort state ?", log.Fields{"Error": msgObj.Result})
+				// possibly force FSM into abort or ignore some errors for some messages? store error for mgmt display?
+				return
+			}
+			if msgObj.EntityClass == oFsm.pOmciCC.pLastTxMeInstance.GetClassID() &&
+				msgObj.EntityInstance == oFsm.pOmciCC.pLastTxMeInstance.GetEntityID() {
+				// maybe we can use just the same eventName for different state transitions like "forward"
+				//   - might be checked, but so far I go for sure and have to inspect the concrete state events ...
+				switch oFsm.pOmciCC.pLastTxMeInstance.GetName() {
+				case "VlanTaggingFilterData":
+					{ // let the FSM proceed ...
+						oFsm.pAdaptFsm.pFsm.Event(vlanEvRxConfigVtfd)
+					}
+				}
+			}
+		} //CreateResponseType
+	case omci.SetResponseType:
+		{
+			msgLayer := (*msg.OmciPacket).Layer(omci.LayerTypeSetResponse)
+			if msgLayer == nil {
+				logger.Error("UniVlanConfigFsm - Omci Msg layer could not be detected for SetResponse")
+				return
+			}
+			msgObj, msgOk := msgLayer.(*omci.SetResponse)
+			if !msgOk {
+				logger.Error("UniVlanConfigFsm - Omci Msg layer could not be assigned for SetResponse")
+				return
+			}
+			logger.Debugw("UniVlanConfigFsm SetResponse Data", log.Fields{"deviceId": oFsm.pAdaptFsm.deviceID, "data-fields": msgObj})
+			if msgObj.Result != me.Success {
+				logger.Errorw("UniVlanConfigFsm - Omci SetResponse Error - later: drive FSM to abort state ?", log.Fields{"Error": msgObj.Result})
+				// possibly force FSM into abort or ignore some errors for some messages? store error for mgmt display?
+				return
+			}
+			if msgObj.EntityClass == oFsm.pOmciCC.pLastTxMeInstance.GetClassID() &&
+				msgObj.EntityInstance == oFsm.pOmciCC.pLastTxMeInstance.GetEntityID() {
+				switch oFsm.pOmciCC.pLastTxMeInstance.GetName() {
+				case "ExtendedVlanTaggingOperationConfigurationData":
+					{ // let the EVTO config proceed by stopping the wait function
+						oFsm.omciMIdsResponseReceived <- true
+					}
+				}
+			}
+		} //SetResponseType
+	default:
+		{
+			logger.Errorw("UniVlanConfigFsm - Rx OMCI unhandled MsgType", log.Fields{"omciMsgType": msg.OmciMsg.MessageType})
+			return
+		}
+	}
+}
+
+func (oFsm *UniVlanConfigFsm) performConfigEvtocdEntries() {
+	{ // for local var
+		// EVTOCD ME is expected to exist at this point already from MIB-Download (with AssociationType/Pointer)
+		// we need to extend the configuration by EthType definition and, to be sure, downstream 'inverse' mode
+		logger.Debugw("UniVlanConfigFsm Tx Set::EVTOCD", log.Fields{
+			"EntitytId":  strconv.FormatInt(int64(oFsm.evtocdID), 16),
+			"i/oEthType": strconv.FormatInt(int64(cDefaultTpid), 16),
+			"device-id":  oFsm.pAdaptFsm.deviceID})
+		meParams := me.ParamData{
+			EntityID: oFsm.evtocdID,
+			Attributes: me.AttributeValueMap{
+				"InputTpid":      uint16(cDefaultTpid), //could be possibly retrieved from flow config one day, by now just like py-code base
+				"OutputTpid":     uint16(cDefaultTpid), //could be possibly retrieved from flow config one day, by now just like py-code base
+				"DownstreamMode": uint8(cDefaultDownstreamMode),
+			},
+		}
+		meInstance := oFsm.pOmciCC.sendSetEvtocdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+			oFsm.pAdaptFsm.commChan, meParams)
+		//accept also nil as (error) return value for writing to LastTx
+		//  - this avoids misinterpretation of new received OMCI messages
+		oFsm.pOmciCC.pLastTxMeInstance = meInstance
+
+		//verify response
+		err := oFsm.waitforOmciResponse()
+		if err != nil {
+			logger.Errorw("Evtocd set TPID failed, aborting VlanConfig FSM!",
+				log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+			oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+			return
+		}
+	} //for local var
+
+	if oFsm.setVid == uint32(of.OfpVlanId_OFPVID_PRESENT) {
+		//transparent transmission required
+		logger.Debugw("UniVlanConfigFsm Tx Set::EVTOCD single tagged transparent rule", log.Fields{
+			"device-id": oFsm.pAdaptFsm.deviceID})
+		sliceEvtocdRule := make([]uint8, 16)
+		// fill vlan tagging operation table bit fields using network=bigEndian order and using slice offset 0 as highest 'word'
+		binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterOuterOffset:],
+			cPrioIgnoreTag<<cFilterPrioOffset| // Not an outer-tag rule
+				cDoNotFilterVid<<cFilterVidOffset| // Do not filter on outer vid
+				cDoNotFilterTPID<<cFilterTpidOffset) // Do not filter on outer TPID field
+
+		binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterInnerOffset:],
+			cPrioDefaultFilter<<cFilterPrioOffset| // default inner-tag rule
+				cDoNotFilterVid<<cFilterVidOffset| // Do not filter on inner vid
+				cDoNotFilterTPID<<cFilterTpidOffset| // Do not filter on inner TPID field
+				cDoNotFilterEtherType<<cFilterEtherTypeOffset) // Do not filter of EtherType
+
+		binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatOuterOffset:],
+			0<<cTreatTTROffset| // Do not pop any tags
+				cDoNotAddPrio<<cTreatPrioOffset| // do not add outer tag
+				cDontCareVid<<cTreatVidOffset| // Outer VID don't care
+				cDontCareTpid<<cTreatTpidOffset) // Outer TPID field don't care
+
+		binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatInnerOffset:],
+			cDoNotAddPrio<<cTreatPrioOffset| // do not add inner tag
+				cDontCareVid<<cTreatVidOffset| // Outer VID don't care
+				cSetOutputTpidCopyDei<<cTreatTpidOffset) // Set TPID = 0x8100
+
+		meParams := me.ParamData{
+			EntityID: oFsm.evtocdID,
+			Attributes: me.AttributeValueMap{
+				"ReceivedFrameVlanTaggingOperationTable": sliceEvtocdRule,
+			},
+		}
+		meInstance := oFsm.pOmciCC.sendSetEvtocdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+			oFsm.pAdaptFsm.commChan, meParams)
+		//accept also nil as (error) return value for writing to LastTx
+		//  - this avoids misinterpretation of new received OMCI messages
+		oFsm.pOmciCC.pLastTxMeInstance = meInstance
+
+		//verify response
+		err := oFsm.waitforOmciResponse()
+		if err != nil {
+			logger.Errorw("Evtocd set transparent singletagged rule failed, aborting VlanConfig FSM!",
+				log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+			oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+			return
+		}
+	} else {
+		// according to py-code acceptIncrementalEvto program option decides upon stacking or translation scenario
+		if oFsm.acceptIncrementalEvtoOption {
+			// this defines VID translation scenario: singletagged->singletagged (if not transparent)
+			logger.Debugw("UniVlanConfigFsm Tx Set::EVTOCD single tagged translation rule", log.Fields{
+				"device-id": oFsm.pAdaptFsm.deviceID})
+			sliceEvtocdRule := make([]uint8, 16)
+			// fill vlan tagging operation table bit fields using network=bigEndian order and using slice offset 0 as highest 'word'
+			binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterOuterOffset:],
+				cPrioIgnoreTag<<cFilterPrioOffset| // Not an outer-tag rule
+					cDoNotFilterVid<<cFilterVidOffset| // Do not filter on outer vid
+					cDoNotFilterTPID<<cFilterTpidOffset) // Do not filter on outer TPID field
+
+			binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterInnerOffset:],
+				oFsm.matchPcp<<cFilterPrioOffset| // either DNFonPrio or ignore tag (default) on innerVLAN
+					oFsm.matchVid<<cFilterVidOffset| // either DNFonVid or real filter VID
+					cDoNotFilterTPID<<cFilterTpidOffset| // Do not filter on inner TPID field
+					cDoNotFilterEtherType<<cFilterEtherTypeOffset) // Do not filter of EtherType
+
+			binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatOuterOffset:],
+				oFsm.tagsToRemove<<cTreatTTROffset| // either 1 or 0
+					cDoNotAddPrio<<cTreatPrioOffset| // do not add outer tag
+					cDontCareVid<<cTreatVidOffset| // Outer VID don't care
+					cDontCareTpid<<cTreatTpidOffset) // Outer TPID field don't care
+
+			binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatInnerOffset:],
+				oFsm.setPcp<<cTreatPrioOffset| // as configured in flow
+					oFsm.setVid<<cTreatVidOffset| //as configured in flow
+					cSetOutputTpidCopyDei<<cTreatTpidOffset) // Set TPID = 0x8100
+
+			meParams := me.ParamData{
+				EntityID: oFsm.evtocdID,
+				Attributes: me.AttributeValueMap{
+					"ReceivedFrameVlanTaggingOperationTable": sliceEvtocdRule,
+				},
+			}
+			meInstance := oFsm.pOmciCC.sendSetEvtocdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+				oFsm.pAdaptFsm.commChan, meParams)
+			//accept also nil as (error) return value for writing to LastTx
+			//  - this avoids misinterpretation of new received OMCI messages
+			oFsm.pOmciCC.pLastTxMeInstance = meInstance
+
+			//verify response
+			err := oFsm.waitforOmciResponse()
+			if err != nil {
+				logger.Errorw("Evtocd set singletagged translation rule failed, aborting VlanConfig FSM!",
+					log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+				oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+				return
+			}
+		} else {
+			//not transparent and not acceptIncrementalEvtoOption untagged/priotagged->singletagged
+			{ // just for local var's
+				// this defines stacking scenario: untagged->singletagged
+				logger.Debugw("UniVlanConfigFsm Tx Set::EVTOCD untagged->singletagged rule", log.Fields{
+					"device-id": oFsm.pAdaptFsm.deviceID})
+				sliceEvtocdRule := make([]uint8, 16)
+				// fill vlan tagging operation table bit fields using network=bigEndian order and using slice offset 0 as highest 'word'
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterOuterOffset:],
+					cPrioIgnoreTag<<cFilterPrioOffset| // Not an outer-tag rule
+						cDoNotFilterVid<<cFilterVidOffset| // Do not filter on outer vid
+						cDoNotFilterTPID<<cFilterTpidOffset) // Do not filter on outer TPID field
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterInnerOffset:],
+					cPrioIgnoreTag<<cFilterPrioOffset| // Not an inner-tag rule
+						cDoNotFilterVid<<cFilterVidOffset| // Do not filter on inner vid
+						cDoNotFilterTPID<<cFilterTpidOffset| // Do not filter on inner TPID field
+						cDoNotFilterEtherType<<cFilterEtherTypeOffset) // Do not filter of EtherType
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatOuterOffset:],
+					0<<cTreatTTROffset| // Do not pop any tags
+						cDoNotAddPrio<<cTreatPrioOffset| // do not add outer tag
+						cDontCareVid<<cTreatVidOffset| // Outer VID don't care
+						cDontCareTpid<<cTreatTpidOffset) // Outer TPID field don't care
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatInnerOffset:],
+					0<<cTreatPrioOffset| // vlan prio set to 0
+						//   (as done in Py code, maybe better option would be setPcp here, which still could be 0?)
+						oFsm.setVid<<cTreatVidOffset| // Outer VID don't care
+						cSetOutputTpidCopyDei<<cTreatTpidOffset) // Set TPID = 0x8100
+
+				meParams := me.ParamData{
+					EntityID: oFsm.evtocdID,
+					Attributes: me.AttributeValueMap{
+						"ReceivedFrameVlanTaggingOperationTable": sliceEvtocdRule,
+					},
+				}
+				meInstance := oFsm.pOmciCC.sendSetEvtocdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+					oFsm.pAdaptFsm.commChan, meParams)
+				//accept also nil as (error) return value for writing to LastTx
+				//  - this avoids misinterpretation of new received OMCI messages
+				oFsm.pOmciCC.pLastTxMeInstance = meInstance
+
+				//verify response
+				err := oFsm.waitforOmciResponse()
+				if err != nil {
+					logger.Errorw("Evtocd set untagged->singletagged rule failed, aborting VlanConfig FSM!",
+						log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+					oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+					return
+				}
+			} //just for local var's
+			{ // just for local var's
+				// this defines 'stacking' scenario: priotagged->singletagged
+				logger.Debugw("UniVlanConfigFsm Tx Set::EVTOCD priotagged->singletagged rule", log.Fields{
+					"device-id": oFsm.pAdaptFsm.deviceID})
+				sliceEvtocdRule := make([]uint8, 16)
+				// fill vlan tagging operation table bit fields using network=bigEndian order and using slice offset 0 as highest 'word'
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterOuterOffset:],
+					cPrioIgnoreTag<<cFilterPrioOffset| // Not an outer-tag rule
+						cDoNotFilterVid<<cFilterVidOffset| // Do not filter on outer vid
+						cDoNotFilterTPID<<cFilterTpidOffset) // Do not filter on outer TPID field
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cFilterInnerOffset:],
+					cPrioDoNotFilter<<cFilterPrioOffset| // Do not Filter on innerprio
+						0<<cFilterVidOffset| // filter on inner vid 0 (prioTagged)
+						cDoNotFilterTPID<<cFilterTpidOffset| // Do not filter on inner TPID field
+						cDoNotFilterEtherType<<cFilterEtherTypeOffset) // Do not filter of EtherType
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatOuterOffset:],
+					1<<cTreatTTROffset| // pop the prio-tag
+						cDoNotAddPrio<<cTreatPrioOffset| // do not add outer tag
+						cDontCareVid<<cTreatVidOffset| // Outer VID don't care
+						cDontCareTpid<<cTreatTpidOffset) // Outer TPID field don't care
+
+				binary.BigEndian.PutUint32(sliceEvtocdRule[cTreatInnerOffset:],
+					cCopyPrioFromInner<<cTreatPrioOffset| // vlan copy from PrioTag
+						//   (as done in Py code, maybe better option would be setPcp here, which still could be PrioCopy?)
+						oFsm.setVid<<cTreatVidOffset| // Outer VID as configured
+						cSetOutputTpidCopyDei<<cTreatTpidOffset) // Set TPID = 0x8100
+
+				meParams := me.ParamData{
+					EntityID: oFsm.evtocdID,
+					Attributes: me.AttributeValueMap{
+						"ReceivedFrameVlanTaggingOperationTable": sliceEvtocdRule,
+					},
+				}
+				meInstance := oFsm.pOmciCC.sendSetEvtocdVar(context.TODO(), ConstDefaultOmciTimeout, true,
+					oFsm.pAdaptFsm.commChan, meParams)
+				//accept also nil as (error) return value for writing to LastTx
+				//  - this avoids misinterpretation of new received OMCI messages
+				oFsm.pOmciCC.pLastTxMeInstance = meInstance
+
+				//verify response
+				err := oFsm.waitforOmciResponse()
+				if err != nil {
+					logger.Errorw("Evtocd set priotagged->singletagged rule failed, aborting VlanConfig FSM!",
+						log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+					oFsm.pAdaptFsm.pFsm.Event(vlanEvReset)
+					return
+				}
+			} //just for local var's
+		}
+	}
+
+	// if Config has been done for all GemPort instances let the FSM proceed
+	logger.Debugw("EVTOCD set loop finished", log.Fields{"device-id": oFsm.pAdaptFsm.deviceID})
+	oFsm.pAdaptFsm.pFsm.Event(vlanEvRxConfigEvtocd)
+	return
+}
+
+func (oFsm *UniVlanConfigFsm) waitforOmciResponse() error {
+	select {
+	// maybe be also some outside cancel (but no context modelled for the moment ...)
+	// case <-ctx.Done():
+	// 		logger.Infow("LockState-bridge-init message reception canceled", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
+	case <-time.After(30 * time.Second): //AS FOR THE OTHER OMCI FSM's
+		logger.Warnw("UniVlanConfigFsm multi entity timeout", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
+		return errors.New("UniVlanConfigFsm multi entity timeout")
+	case success := <-oFsm.omciMIdsResponseReceived:
+		if success == true {
+			logger.Debug("UniVlanConfigFsm multi entity response received")
+			return nil
+		}
+		// should not happen so far
+		logger.Warnw("UniVlanConfigFsm multi entity response error", log.Fields{"for device-id": oFsm.pAdaptFsm.deviceID})
+		return errors.New("UniVlanConfigFsm multi entity responseError")
+	}
+}
diff --git a/internal/pkg/onuadaptercore/onu_device_db.go b/internal/pkg/onuadaptercore/onu_device_db.go
index aa12728..4b80a94 100644
--- a/internal/pkg/onuadaptercore/onu_device_db.go
+++ b/internal/pkg/onuadaptercore/onu_device_db.go
@@ -64,16 +64,21 @@
 	} else {
 		meAttribs, ok := onuDeviceDB.meDb[meClassId][meEntityId]
 		if !ok {
+			/* verbose logging, avoid in >= debug level
 			logger.Debugw("meEntityId not found - add to db :", log.Fields{"device-id": onuDeviceDB.pOnuDeviceEntry.deviceID})
+			*/
 			onuDeviceDB.meDb[meClassId][meEntityId] = meAttributes
 		} else {
+			/* verbose logging, avoid in >= debug level
 			logger.Debugw("ME-Instance exists already: merge attribute data :", log.Fields{"device-id": onuDeviceDB.pOnuDeviceEntry.deviceID, "meAttribs": meAttribs})
-
+			*/
 			for k, v := range meAttributes {
 				meAttribs[k] = v
 			}
 			onuDeviceDB.meDb[meClassId][meEntityId] = meAttribs
+			/* verbose logging, avoid in >= debug level
 			logger.Debugw("ME-Instance updated :", log.Fields{"device-id": onuDeviceDB.pOnuDeviceEntry.deviceID, "meAttribs": meAttribs})
+			*/
 		}
 	}
 }
@@ -81,8 +86,10 @@
 func (onuDeviceDB *OnuDeviceDB) GetMe(meClassId me.ClassID, meEntityId uint16) me.AttributeValueMap {
 
 	if meAttributes, present := onuDeviceDB.meDb[meClassId][meEntityId]; present {
+		/* verbose logging, avoid in >= debug level
 		logger.Debugw("ME found:", log.Fields{"meClassId": meClassId, "meEntityId": meEntityId, "meAttributes": meAttributes,
 			"device-id": onuDeviceDB.pOnuDeviceEntry.deviceID})
+		*/
 		return meAttributes
 	} else {
 		return nil
diff --git a/internal/pkg/onuadaptercore/onu_device_entry.go b/internal/pkg/onuadaptercore/onu_device_entry.go
index a29bf05..154e513 100644
--- a/internal/pkg/onuadaptercore/onu_device_entry.go
+++ b/internal/pkg/onuadaptercore/onu_device_entry.go
@@ -110,16 +110,17 @@
 
 const (
 	// Events of interest to Device Adapters and OpenOMCI State Machines
-	DeviceStatusInit     OnuDeviceEvent = 0 // OnuDeviceEntry default start state
-	MibDatabaseSync      OnuDeviceEvent = 1 // MIB database sync (upload done)
-	OmciCapabilitiesDone OnuDeviceEvent = 2 // OMCI ME and message type capabilities known
-	MibDownloadDone      OnuDeviceEvent = 3 // MIB database sync (upload done)
-	UniLockStateDone     OnuDeviceEvent = 4 // Uni ports admin set to lock
-	UniUnlockStateDone   OnuDeviceEvent = 5 // Uni ports admin set to unlock
-	UniAdminStateDone    OnuDeviceEvent = 6 // Uni ports admin set done - general
-	PortLinkUp           OnuDeviceEvent = 7 // Port link state change
-	PortLinkDw           OnuDeviceEvent = 8 // Port link state change
-	OmciAniConfigDone    OnuDeviceEvent = 9 // AniSide config according to TechProfile done
+	DeviceStatusInit     OnuDeviceEvent = 0  // OnuDeviceEntry default start state
+	MibDatabaseSync      OnuDeviceEvent = 1  // MIB database sync (upload done)
+	OmciCapabilitiesDone OnuDeviceEvent = 2  // OMCI ME and message type capabilities known
+	MibDownloadDone      OnuDeviceEvent = 3  // MIB database sync (upload done)
+	UniLockStateDone     OnuDeviceEvent = 4  // Uni ports admin set to lock
+	UniUnlockStateDone   OnuDeviceEvent = 5  // Uni ports admin set to unlock
+	UniAdminStateDone    OnuDeviceEvent = 6  // Uni ports admin set done - general
+	PortLinkUp           OnuDeviceEvent = 7  // Port link state change
+	PortLinkDw           OnuDeviceEvent = 8  // Port link state change
+	OmciAniConfigDone    OnuDeviceEvent = 9  // AniSide config according to TechProfile done
+	OmciVlanFilterDone   OnuDeviceEvent = 10 // Omci Vlan config according to flowConfig done
 	// Add other events here as needed (alarms separate???)
 )
 
diff --git a/internal/pkg/onuadaptercore/onu_uni_tp.go b/internal/pkg/onuadaptercore/onu_uni_tp.go
index 728cac8..a5a8ba4 100644
--- a/internal/pkg/onuadaptercore/onu_uni_tp.go
+++ b/internal/pkg/onuadaptercore/onu_uni_tp.go
@@ -67,8 +67,9 @@
 }
 
 type tTechProfileIndication struct {
-	techProfileType string
-	techProfileID   uint16
+	techProfileType       string
+	techProfileID         uint16
+	techProfileConfigDone bool
 }
 
 type tcontParamStruct struct {
@@ -100,19 +101,21 @@
 
 //OnuUniTechProf structure holds information about the TechProfiles attached to Uni Ports of the ONU
 type OnuUniTechProf struct {
-	deviceID           string
-	baseDeviceHandler  *DeviceHandler
-	tpProcMutex        sync.RWMutex
-	mapUniTpPath       map[uint32]string
-	sOnuPersistentData onuPersistentData
-	techProfileKVStore *db.Backend
-	onuKVStore         *db.Backend
-	onuKVStorePath     string
-	chTpProcessingStep chan uint8
-	mapUniTpIndication map[uint32]*tTechProfileIndication //use pointer values to ease assignments to the map
-	mapPonAniConfig    map[uint32]*tMapPonAniConfig       //per UNI: use pointer values to ease assignments to the map
-	pAniConfigFsm      *UniPonAniConfigFsm
-	procResult         error //error indication of processing
+	deviceID                 string
+	baseDeviceHandler        *DeviceHandler
+	tpProcMutex              sync.RWMutex
+	mapUniTpPath             map[uint32]string
+	sOnuPersistentData       onuPersistentData
+	techProfileKVStore       *db.Backend
+	onuKVStore               *db.Backend
+	onuKVStorePath           string
+	chTpConfigProcessingStep chan uint8
+	chTpKvProcessingStep     chan uint8
+	mapUniTpIndication       map[uint8]*tTechProfileIndication //use pointer values to ease assignments to the map
+	mapPonAniConfig          map[uint8]*tMapPonAniConfig       //per UNI: use pointer values to ease assignments to the map
+	pAniConfigFsm            *UniPonAniConfigFsm
+	procResult               error //error indication of processing
+	mutexTPState             sync.Mutex
 }
 
 //NewOnuUniTechProf returns the instance of a OnuUniTechProf
@@ -125,9 +128,10 @@
 	onuTP.tpProcMutex = sync.RWMutex{}
 	onuTP.mapUniTpPath = make(map[uint32]string)
 	onuTP.sOnuPersistentData.PersUniTpPath = make([]uniPersData, 1)
-	onuTP.chTpProcessingStep = make(chan uint8)
-	onuTP.mapUniTpIndication = make(map[uint32]*tTechProfileIndication)
-	onuTP.mapPonAniConfig = make(map[uint32]*tMapPonAniConfig)
+	onuTP.chTpConfigProcessingStep = make(chan uint8)
+	onuTP.chTpKvProcessingStep = make(chan uint8)
+	onuTP.mapUniTpIndication = make(map[uint8]*tTechProfileIndication)
+	onuTP.mapPonAniConfig = make(map[uint8]*tMapPonAniConfig)
 	onuTP.procResult = nil //default assumption processing done with success
 
 	onuTP.techProfileKVStore = aDeviceHandler.SetBackend(cBasePathTechProfileKVStore)
@@ -214,9 +218,9 @@
 // configureUniTp checks existing tp resources to delete and starts the corresponding OMCI configuation of the UNI port
 // all possibly blocking processing must be run in background to allow for deadline supervision!
 // but take care on sequential background processing when needed (logical dependencies)
-//   use waitForTimeoutOrCompletion(ctx, processingStep) for internal synchronisation
+//   use waitForTimeoutOrCompletion(ctx, chTpConfigProcessingStep, processingStep) for internal synchronisation
 func (onuTP *OnuUniTechProf) configureUniTp(ctx context.Context,
-	aUniID uint32, aPathString string, wg *sync.WaitGroup) {
+	aUniID uint8, aPathString string, wg *sync.WaitGroup) {
 	defer wg.Done() //always decrement the waitGroup on return
 	logger.Debugw("configure the Uni according to TpPath", log.Fields{
 		"device-id": onuTP.deviceID, "uniID": aUniID, "path": aPathString})
@@ -243,7 +247,7 @@
 		return
 	}
 
-	var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpProcessingStep
+	var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpConfigProcessingStep
 
 	//according to updateOnuUniTpPath() logic the assumption here is, that this configuration is only called
 	//  in case the KVPath has changed for the given UNI,
@@ -254,7 +258,7 @@
 	//TODO!!!:
 	/* if tcontMap  not empty {
 		go onuTP.deleteAniSideConfig(ctx, aUniID, processingStep)
-		if !onuTP.waitForTimeoutOrCompletion(ctx, processingStep) {
+		if !onuTP.waitForTimeoutOrCompletion(ctx, chTpConfigProcessingStep, processingStep) {
 			//timeout or error detected
 			return
 		}
@@ -264,7 +268,7 @@
 	processingStep++
 	*/
 	go onuTP.readAniSideConfigFromTechProfile(ctx, aUniID, aPathString, processingStep)
-	if !onuTP.waitForTimeoutOrCompletion(ctx, processingStep) {
+	if !onuTP.waitForTimeoutOrCompletion(ctx, onuTP.chTpConfigProcessingStep, processingStep) {
 		//timeout or error detected
 		logger.Debugw("tech-profile related configuration aborted on read",
 			log.Fields{"device-id": onuTP.deviceID, "UniId": aUniID})
@@ -277,7 +281,7 @@
 		if _, existTG := (*valuePA)[0]; existTG {
 			//Config data for this uni and and at least TCont Index 0 exist
 			go onuTP.setAniSideConfigFromTechProfile(ctx, aUniID, pCurrentUniPort, processingStep)
-			if !onuTP.waitForTimeoutOrCompletion(ctx, processingStep) {
+			if !onuTP.waitForTimeoutOrCompletion(ctx, onuTP.chTpConfigProcessingStep, processingStep) {
 				//timeout or error detected
 				logger.Debugw("tech-profile related configuration aborted on set",
 					log.Fields{"device-id": onuTP.deviceID, "UniId": aUniID})
@@ -311,9 +315,9 @@
 		onuTP.procResult = errors.New("ONU/TP-data update aborted: onuKVStore not set")
 		return
 	}
-	var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpProcessingStep
+	var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpKvProcessingStep
 	go onuTP.storePersistentData(ctx, processingStep)
-	if !onuTP.waitForTimeoutOrCompletion(ctx, processingStep) {
+	if !onuTP.waitForTimeoutOrCompletion(ctx, onuTP.chTpKvProcessingStep, processingStep) {
 		//timeout or error detected
 		logger.Debugw("ONU/TP-data not written - abort", log.Fields{"device-id": onuTP.deviceID})
 		onuTP.procResult = errors.New("ONU/TP-data update aborted: during writing process")
@@ -355,9 +359,9 @@
 	//TODO!!!
 	//delete the given resource from ONU OMCI config and data base - as background routine
 	/*
-		var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpProcessingStep
+		var processingStep uint8 = 1 // used to synchronize the different processing steps with chTpConfigProcessingStep
 		go onuTp.deleteAniResource(ctx, processingStep)
-		if !onuTP.waitForTimeoutOrCompletion(ctx, processingStep) {
+		if !onuTP.waitForTimeoutOrCompletion(ctx, chTpConfigProcessingStep, processingStep) {
 			//timeout or error detected
 			return
 		}
@@ -387,16 +391,16 @@
 	if err != nil {
 		logger.Errorw("unable to marshal ONU/TP-data", log.Fields{"onuTP.sOnuPersistentData": onuTP.sOnuPersistentData,
 			"device-id": onuTP.deviceID, "err": err})
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpKvProcessingStep <- 0 //error indication
 		return
 	}
 	err = onuTP.onuKVStore.Put(ctx, onuTP.onuKVStorePath, Value)
 	if err != nil {
 		logger.Errorw("unable to write ONU/TP-data into KVstore", log.Fields{"device-id": onuTP.deviceID, "err": err})
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpKvProcessingStep <- 0 //error indication
 		return
 	}
-	onuTP.chTpProcessingStep <- aProcessingStep //done
+	onuTP.chTpKvProcessingStep <- aProcessingStep //done
 }
 
 func (onuTP *OnuUniTechProf) restorePersistentData(ctx context.Context) error {
@@ -446,7 +450,7 @@
 }
 
 func (onuTP *OnuUniTechProf) readAniSideConfigFromTechProfile(
-	ctx context.Context, aUniID uint32, aPathString string, aProcessingStep uint8) {
+	ctx context.Context, aUniID uint8, aPathString string, aProcessingStep uint8) {
 	var tpInst tp.TechProfile
 
 	//store profile type and identifier for later usage within the OMCI identifier and possibly ME setup
@@ -455,7 +459,7 @@
 	if len(subStringSlice) <= 2 {
 		logger.Errorw("invalid path name format",
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
 
@@ -483,7 +487,7 @@
 	if err != nil {
 		logger.Errorw("invalid ProfileId from path",
 			log.Fields{"ParseErr": err})
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
 
@@ -504,7 +508,7 @@
 			if err = json.Unmarshal(tpTmpBytes, &tpInst); err != nil {
 				logger.Errorw("TechProf - Failed to unmarshal tech-profile into tpInst",
 					log.Fields{"error": err, "device-id": onuTP.deviceID})
-				onuTP.chTpProcessingStep <- 0 //error indication
+				onuTP.chTpConfigProcessingStep <- 0 //error indication
 				return
 			}
 			logger.Debugw("TechProf - tpInst", log.Fields{"tpInst": tpInst})
@@ -515,13 +519,13 @@
 		} else {
 			logger.Errorw("No tech-profile found",
 				log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
-			onuTP.chTpProcessingStep <- 0 //error indication
+			onuTP.chTpConfigProcessingStep <- 0 //error indication
 			return
 		}
 	} else {
 		logger.Errorw("kvstore-get failed for path",
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
 
@@ -569,7 +573,7 @@
 				log.Fields{"device-id": onuTP.deviceID, "index": pos, "PrioQueue": content.PriorityQueue})
 			//remove PonAniConfig  as done so far, delete map should be safe, even if not existing
 			delete(onuTP.mapPonAniConfig, aUniID)
-			onuTP.chTpProcessingStep <- 0 //error indication
+			onuTP.chTpConfigProcessingStep <- 0 //error indication
 			return
 		}
 		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].prioQueueIndex =
@@ -594,7 +598,7 @@
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
 		//remove PonAniConfig  as done so far, delete map should be safe, even if not existing
 		delete(onuTP.mapPonAniConfig, aUniID)
-		onuTP.chTpProcessingStep <- 0 //error indication
+		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
 	//TODO!! MC (downstream) GemPorts can be set using DownstreamGemPortAttributeList seperately
@@ -610,15 +614,15 @@
 			"QueueScheduling": gemEntry.queueSchedPolicy})
 	}
 
-	onuTP.chTpProcessingStep <- aProcessingStep //done
+	onuTP.chTpConfigProcessingStep <- aProcessingStep //done
 }
 
 func (onuTP *OnuUniTechProf) setAniSideConfigFromTechProfile(
-	ctx context.Context, aUniID uint32, apCurrentUniPort *OnuUniPort, aProcessingStep uint8) {
+	ctx context.Context, aUniID uint8, apCurrentUniPort *OnuUniPort, aProcessingStep uint8) {
 
 	//OMCI transfer of ANI data acc. to mapPonAniConfig
 	// also the FSM's are running in background,
-	//   hence we have to make sure they indicate 'success' success on chTpProcessingStep with aProcessingStep
+	//   hence we have to make sure they indicate 'success' success on chTpConfigProcessingStep with aProcessingStep
 	if onuTP.pAniConfigFsm == nil {
 		onuTP.createAniConfigFsm(aUniID, apCurrentUniPort, OmciAniConfigDone, aProcessingStep)
 	} else { //AniConfigFsm already init
@@ -627,13 +631,13 @@
 }
 
 func (onuTP *OnuUniTechProf) waitForTimeoutOrCompletion(
-	ctx context.Context, aProcessingStep uint8) bool {
+	ctx context.Context, aChTpProcessingStep <-chan uint8, aProcessingStep uint8) bool {
 	select {
 	case <-ctx.Done():
 		logger.Warnw("processing not completed in-time: force release of TpProcMutex!",
 			log.Fields{"device-id": onuTP.deviceID, "error": ctx.Err()})
 		return false
-	case rxStep := <-onuTP.chTpProcessingStep:
+	case rxStep := <-aChTpProcessingStep:
 		if rxStep == aProcessingStep {
 			return true
 		}
@@ -646,7 +650,7 @@
 }
 
 // createUniLockFsm initialises and runs the AniConfig FSM to transfer the OMCI related commands for ANI side configuration
-func (onuTP *OnuUniTechProf) createAniConfigFsm(aUniID uint32,
+func (onuTP *OnuUniTechProf) createAniConfigFsm(aUniID uint8,
 	apCurrentUniPort *OnuUniPort, devEvent OnuDeviceEvent, aProcessingStep uint8) {
 	logger.Debugw("createAniConfigFsm", log.Fields{"device-id": onuTP.deviceID})
 	chAniConfigFsm := make(chan Message, 2048)
@@ -676,7 +680,7 @@
 	if pACStatemachine != nil {
 		if pACStatemachine.Is(aniStDisabled) {
 			//FSM init requirement to get informed abou FSM completion! (otherwise timeout of the TechProf config)
-			onuTP.pAniConfigFsm.SetFsmCompleteChannel(onuTP.chTpProcessingStep, aProcessingStep)
+			onuTP.pAniConfigFsm.SetFsmCompleteChannel(onuTP.chTpConfigProcessingStep, aProcessingStep)
 			if err := pACStatemachine.Event(aniEvStart); err != nil {
 				logger.Warnw("AniConfigFSM: can't start", log.Fields{"err": err})
 				// maybe try a FSM reset and then again ... - TODO!!!
@@ -695,3 +699,25 @@
 		// maybe try a FSM reset and then again ... - TODO!!!
 	}
 }
+
+// setConfigDone sets the requested techProfile config state (if possible)
+func (onuTP *OnuUniTechProf) setConfigDone(aUniID uint8, aState bool) {
+	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
+		onuTP.mutexTPState.Lock()
+		onuTP.mapUniTpIndication[aUniID].techProfileConfigDone = aState
+		onuTP.mutexTPState.Unlock()
+	} //else: the state is just ignored (does not exist)
+}
+
+// getTechProfileDone checks if the Techprofile processing with the requested TechProfile ID was done
+func (onuTP *OnuUniTechProf) getTechProfileDone(aUniID uint8, aTpID uint16) bool {
+	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
+		if onuTP.mapUniTpIndication[aUniID].techProfileID == aTpID {
+			onuTP.mutexTPState.Lock()
+			defer onuTP.mutexTPState.Unlock()
+			return onuTP.mapUniTpIndication[aUniID].techProfileConfigDone
+		}
+	}
+	//for all other constellations indicate false = Config not done
+	return false
+}
diff --git a/internal/pkg/onuadaptercore/openonu.go b/internal/pkg/onuadaptercore/openonu.go
index 5dcf108..c9d4b37 100644
--- a/internal/pkg/onuadaptercore/openonu.go
+++ b/internal/pkg/onuadaptercore/openonu.go
@@ -52,6 +52,7 @@
 	exitChannel                 chan int
 	HeartbeatCheckInterval      time.Duration
 	HeartbeatFailReportInterval time.Duration
+	AcceptIncrementalEvto       bool
 	//GrpcTimeoutInterval         time.Duration
 	lockDeviceHandlersMap sync.RWMutex
 	pSupportedFsms        *OmciDeviceFsms
@@ -77,6 +78,7 @@
 	openOnuAc.KVStoreTimeout = cfg.KVStoreTimeout
 	openOnuAc.HeartbeatCheckInterval = cfg.HeartbeatCheckInterval
 	openOnuAc.HeartbeatFailReportInterval = cfg.HeartbeatFailReportInterval
+	openOnuAc.AcceptIncrementalEvto = cfg.AccIncrEvto
 	//openOnuAc.GrpcTimeoutInterval = cfg.GrpcTimeoutInterval
 	openOnuAc.lockDeviceHandlersMap = sync.RWMutex{}
 
@@ -304,10 +306,32 @@
 }
 
 //Update_flows_incrementally updates (add/remove) the flows on a given device
-func (oo *OpenONUAC) Update_flows_incrementally(device *voltha.Device, flows *openflow_13.FlowChanges, groups *openflow_13.FlowGroupChanges, flowMetadata *voltha.FlowMetadata) error {
-	//	return errors.New("unImplemented")
-	// testwise: just acknowledge to see, if that avoids ongoing TechProfile config sequences ...
-	return nil
+func (oo *OpenONUAC) Update_flows_incrementally(device *voltha.Device,
+	flows *openflow_13.FlowChanges, groups *openflow_13.FlowGroupChanges, flowMetadata *voltha.FlowMetadata) error {
+	// no point in pushing omci flows if the device isn't reachable
+	if device.ConnectStatus != voltha.ConnectStatus_REACHABLE ||
+		device.AdminState != voltha.AdminState_ENABLED {
+		logger.Warnw("device disabled or offline - skipping flow-update", log.Fields{"deviceId": device.Id})
+		return errors.New("non-matching device state")
+	}
+
+	// For now, there is no support for group changes (as in the actual Py-adapter code)
+	if groups.ToAdd != nil && groups.ToAdd.Items != nil {
+		logger.Debugw("Update-flow-incr: group add not supported (ignored)", log.Fields{"deviceId": device.Id})
+	}
+	if groups.ToRemove != nil && groups.ToRemove.Items != nil {
+		logger.Debugw("Update-flow-incr: group remove not supported (ignored)", log.Fields{"deviceId": device.Id})
+	}
+	if groups.ToUpdate != nil && groups.ToUpdate.Items != nil {
+		logger.Debugw("Update-flow-incr: group update not supported (ignored)", log.Fields{"deviceId": device.Id})
+	}
+
+	if handler := oo.getDeviceHandler(device.Id); handler != nil {
+		err := handler.FlowUpdateIncremental(flows, groups, flowMetadata)
+		return err
+	}
+	logger.Warnw("no handler found for incremental flow update", log.Fields{"deviceId": device.Id})
+	return fmt.Errorf(fmt.Sprintf("handler-not-found-%s", device.Id))
 }
 
 //Update_pm_config returns PmConfigs nil or error