public DeviceElementHierarchies(IEnumerable <ISODevice> devices,
                                        RepresentationMapper representationMapper,
                                        bool mergeBins,
                                        IEnumerable <ISOTimeLog> timeLogs,
                                        string dataPath)
        {
            Items = new Dictionary <string, DeviceHierarchyElement>();

            //Track any device element geometries not logged as a DPT
            Dictionary <string, List <string> > missingGeometryDefinitions = new Dictionary <string, List <string> >();

            foreach (ISODevice device in devices)
            {
                ISODeviceElement rootDeviceElement = device.DeviceElements.SingleOrDefault(det => det.DeviceElementType == ISODeviceElementType.Device);
                if (rootDeviceElement != null)
                {
                    DeviceHierarchyElement hierarchyElement = new DeviceHierarchyElement(rootDeviceElement, 0, representationMapper, mergeBins, missingGeometryDefinitions);
                    hierarchyElement.HandleBinDeviceElements();
                    Items.Add(device.DeviceId, hierarchyElement);
                }
            }

            //Address the missing geometry data with targeted reads of the TLG binaries for any DPDs
            if (missingGeometryDefinitions.Any())
            {
                FillDPDGeometryDefinitions(missingGeometryDefinitions, timeLogs, dataPath);
            }
        }
        public ISODeviceElement GetISODeviceElementFromID(string deviceElementID)
        {
            DeviceHierarchyElement hierarchyElement = GetMatchingElement(deviceElementID);

            if (hierarchyElement != null)
            {
                return(hierarchyElement.DeviceElement);
            }
            return(null);
        }
        public DeviceHierarchyElement GetRootDeviceElementHierarchy()
        {
            DeviceHierarchyElement item = this;

            while (item.Parent != null)
            {
                item = item.Parent;
            }
            return(item);
        }
 public DeviceHierarchyElement GetMatchingElement(string isoDeviceElementId)
 {
     foreach (DeviceHierarchyElement hierarchy in this.Items.Values)
     {
         DeviceHierarchyElement foundModel = hierarchy.FromDeviceElementID(isoDeviceElementId);
         if (foundModel != null)
         {
             return(foundModel);
         }
     }
     return(null);
 }
 public DeviceHierarchyElement FromDeviceElementID(string deviceElementID)
 {
     if (DeviceElement?.DeviceElementId == deviceElementID)
     {
         return(this);
     }
     else if (Children != null)
     {
         foreach (DeviceHierarchyElement child in Children)
         {
             DeviceHierarchyElement childModel = child.FromDeviceElementID(deviceElementID);
             if (childModel != null)
             {
                 return(childModel);
             }
         }
     }
     return(null);
 }
        /// <summary>
        /// Perform a targeted read of the Timelog binary files to obtain implement geometry details logged via DeviceProcessData
        /// </summary>
        /// <param name="missingDefinitions"></param>
        /// <param name="timeLogTimeElements"></param>
        /// <param name="taskDataPath"></param>
        /// <param name="allDeviceHierarchyElements"></param>
        public void FillDPDGeometryDefinitions(Dictionary <string, List <string> > missingDefinitions, IEnumerable <ISOTimeLog> timeLogs, string taskDataPath)
        {
            Dictionary <string, int?> reportedValues = new Dictionary <string, int?>(); //DLV signature / value

            foreach (ISOTimeLog timeLog in timeLogs)
            {
                ISOTime time = timeLog.GetTimeElement(taskDataPath);
                if (time != null &&
                    time.DataLogValues.Any(dlv => missingDefinitions.ContainsKey(dlv.DeviceElementIdRef)))
                {
                    List <ISODataLogValue> dlvsToRead = time.DataLogValues.Where(dlv => missingDefinitions.ContainsKey(dlv.DeviceElementIdRef) &&
                                                                                 missingDefinitions[dlv.DeviceElementIdRef].Contains(dlv.ProcessDataDDI)).ToList();
                    foreach (string deviceElementID in missingDefinitions.Keys)
                    {
                        List <string> ddis = missingDefinitions[deviceElementID];
                        if (ddis.Any(d => d == "0046"))
                        {
                            //We used 0046 generically for a missing width.  Check for any 0044 or 0043
                            var defaultWidthDLV = time.DataLogValues.FirstOrDefault(gt => gt.ProcessDataDDI == "0044" && gt.DeviceElementIdRef == deviceElementID);
                            if (defaultWidthDLV != null)
                            {
                                dlvsToRead.Add(defaultWidthDLV);
                            }
                            else
                            {
                                var workingWidthDLV = time.DataLogValues.FirstOrDefault(gt => gt.ProcessDataDDI == "0043" && gt.DeviceElementIdRef == deviceElementID);
                                if (workingWidthDLV != null)
                                {
                                    dlvsToRead.Add(workingWidthDLV);
                                }
                            }
                        }
                    }

                    if (dlvsToRead.Any())
                    {
                        string binaryPath = System.IO.Path.Combine(taskDataPath, timeLog.Filename + ".bin");
                        Dictionary <byte, int> timelogValues = Mappers.TimeLogMapper.ReadImplementGeometryValues(dlvsToRead.Select(d => d.Index), time, binaryPath);

                        foreach (byte reportedDLVIndex in timelogValues.Keys)
                        {
                            ISODataLogValue reportedDLV = dlvsToRead.First(d => d.Index == reportedDLVIndex);
                            string          dlvKey      = DeviceHierarchyElement.GetDataLogValueKey(reportedDLV);
                            if (!reportedValues.ContainsKey(dlvKey))
                            {
                                //First occurence of this DET and DDI in the timelogs
                                //We take the max width/max (from 0) offset from the 1st timelog.  This matches existing functionality, and is workable for foreseeable cases where working width is the only dynamic value.
                                //ISOXML supports changing widths and offsets dynamically throughout and across timelogs.
                                //Should max width and/or offset parameters change dynamically, data consumers will need to obtain this information via the OperationData.SpatialRecords as is commonly done with 0043 (working width) today
                                //An alternative would be to enhance this logic to clone the entire DeviceModel hierarchy for each variation in offset value, should such data ever occur in the field.
                                reportedValues.Add(dlvKey, timelogValues[reportedDLV.Index]);

                                //Add to this element
                                var matchingElement = GetMatchingElement(reportedDLV.DeviceElementIdRef);
                                if (matchingElement != null)
                                {
                                    switch (reportedDLV.ProcessDataDDI)
                                    {
                                    case "0046":
                                    case "0044":
                                    case "0043":
                                        if (matchingElement.Width == null || timelogValues[reportedDLV.Index] > matchingElement.Width)
                                        {
                                            //If max 0043 is greater than 0046, then take max 0043
                                            matchingElement.Width    = timelogValues[reportedDLV.Index];
                                            matchingElement.WidthDDI = reportedDLV.ProcessDataDDI;
                                        }
                                        break;

                                    case "0086":
                                        matchingElement.XOffset = timelogValues[reportedDLV.Index];
                                        break;

                                    case "0087":
                                        matchingElement.YOffset = timelogValues[reportedDLV.Index];
                                        break;

                                    case "0088":
                                        matchingElement.ZOffset = timelogValues[reportedDLV.Index];
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        public DeviceHierarchyElement(ISODeviceElement deviceElement,
                                      int depth,
                                      RepresentationMapper representationMapper,
                                      bool mergeSingleBinsIntoBoom,
                                      Dictionary <string, List <string> > missingGeometryDefinitions,
                                      HashSet <int> crawledElements = null,
                                      DeviceHierarchyElement parent = null)
        {
            RepresentationMapper = representationMapper;
            MergedElements       = new List <ISODeviceElement>();
            //This Hashset will track that we don't build infinite hierarchies.
            //The plugin does not support peer control at this time.
            _crawledElements = crawledElements;
            if (_crawledElements == null)
            {
                _crawledElements = new HashSet <int>();
            }

            if (_crawledElements.Add((int)deviceElement.DeviceElementObjectId))
            {
                Type          = deviceElement.DeviceElementType;
                DeviceElement = deviceElement;
                Depth         = depth;
                Order         = (int)deviceElement.DeviceElementNumber; //Using this number as analagous to this purpose.  The ISO spec requires these numbers increment from left to right as in ADAPT.  (See ISO11783-10:2015(E) B.3.2 Element number)

                //DeviceProperty assigned Widths & Offsets
                //DeviceProcessData assigned values will be assigned as the SectionMapper reads timelog data.

                //Width
                ISODeviceProperty widthProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0046"); //Max width
                if (widthProperty != null)
                {
                    Width    = widthProperty.Value;
                    WidthDDI = "0046";
                }
                else
                {
                    widthProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0044"); //Default working width
                    if (widthProperty != null)
                    {
                        Width    = widthProperty.Value;
                        WidthDDI = "0044";
                    }

                    if (widthProperty == null)
                    {
                        widthProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0043"); //Actual working width
                        if (widthProperty != null)
                        {
                            Width    = widthProperty.Value;
                            WidthDDI = "0043";
                        }
                    }
                }

                if (Width == null)
                {
                    //We are missing a width DPT.   Log it (0046 as a substitute here for any valid width) for possible retrieval from the TLG binaries.
                    AddMissingGeometryDefinition(missingGeometryDefinitions, deviceElement.DeviceElementId, "0046");
                }

                //Offsets
                ISODeviceProperty xOffsetProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0086");
                if (xOffsetProperty != null)
                {
                    XOffset = xOffsetProperty.Value;
                }
                else
                {
                    AddMissingGeometryDefinition(missingGeometryDefinitions, deviceElement.DeviceElementId, "0086");
                }

                ISODeviceProperty yOffsetProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0087");
                if (yOffsetProperty != null)
                {
                    YOffset = yOffsetProperty.Value;
                }
                else
                {
                    AddMissingGeometryDefinition(missingGeometryDefinitions, deviceElement.DeviceElementId, "0087");
                }

                ISODeviceProperty zOffsetProperty = deviceElement.DeviceProperties.FirstOrDefault(dpt => dpt.DDI == "0088");
                if (zOffsetProperty != null)
                {
                    ZOffset = zOffsetProperty.Value;
                }
                else
                {
                    AddMissingGeometryDefinition(missingGeometryDefinitions, deviceElement.DeviceElementId, "0088");
                }

                //Children
                IEnumerable <ISODeviceElement> childDeviceElements = deviceElement.Device.DeviceElements.Where(det => det.ParentObjectId == deviceElement.DeviceElementObjectId && det.ParentObjectId != det.DeviceElementObjectId); //Do not create children for an element classified as its own parent
                if (childDeviceElements.Any())
                {
                    int childDepth = depth + 1;
                    Children = new List <DeviceHierarchyElement>();
                    foreach (ISODeviceElement childDeviceElement in childDeviceElements)
                    {
                        //If there is a single bin child of the boom (usually alongside sections),
                        //we can logically combine the bin and boom to have a clean hierarchy
                        //where sections are the direct children of the element containing the rates.
                        //We currently use an import property (MergeSingleBinsIntoBoom) to enable this functionality.
                        //Note that if there are any duplicate DDIs on both the Bin and Boom (non-standard per Annex F.3),
                        //the FirstOrDefault() logic in the setter methods in the SpatialRecordMapper will prefer the Boom and suppress the Bin data.
                        if (mergeSingleBinsIntoBoom &&
                            (DeviceElement.DeviceElementType == ISODeviceElementType.Device || DeviceElement.DeviceElementType == ISODeviceElementType.Function) &&
                            childDeviceElement.DeviceElementType == ISODeviceElementType.Bin &&
                            childDeviceElements.Count(c => c.DeviceElementType == ISODeviceElementType.Bin) == 1)
                        {
                            //Set the element into the MergedElements list
                            MergedElements.Add(childDeviceElement);

                            //Set its children as children of the boom
                            foreach (ISODeviceElement binChild in childDeviceElement.ChildDeviceElements.Where(det => det.ParentObjectId == childDeviceElement.DeviceElementObjectId && det.ParentObjectId != det.DeviceElementObjectId))
                            {
                                Children.Add(new DeviceHierarchyElement(binChild, childDepth, representationMapper, mergeSingleBinsIntoBoom, missingGeometryDefinitions, _crawledElements, this));
                            }

                            //This functionality will not work in the ADAPT framework today for multiple bins on one boom (i.e., ISO 11783-10:2015(E) F.23 & F.33).
                            //For these, we will fall back to the more basic default functionality in HandleBinDeviceElements()
                            //where we separate bins and sections into different depths within the ADAPT device hierarchy.
                            //Plugin implementers will need to rationalize the separate bins to the single boom,
                            //with the rate for each bin associated to the corresponding DeviceElement in the ADAPT model.
                            //Were this multi-bin/single boom DDOP common, we could perhaps extend the WorkingData(?) class with some new piece of information
                            //To differentiate like data elements from different bins and thereby extend the merge functionality to this case.
                        }
                        else
                        {
                            //Add the child device element
                            DeviceHierarchyElement child = new DeviceHierarchyElement(childDeviceElement, childDepth, representationMapper, mergeSingleBinsIntoBoom, missingGeometryDefinitions, _crawledElements, this);
                            Children.Add(child);
                        }
                    }
                }

                //Parent
                Parent = parent;
            }
        }