| moel@324 |      1 | /*
 | 
| moel@324 |      2 |  
 | 
| moel@344 |      3 |   This Source Code Form is subject to the terms of the Mozilla Public
 | 
| moel@344 |      4 |   License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
| moel@344 |      5 |   file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
| moel@324 |      6 |  
 | 
| moel@403 |      7 |   Copyright (C) 2009-2013 Michael Möller <mmoeller@openhardwaremonitor.org>
 | 
| moel@344 |      8 | 	Copyright (C) 2010 Paul Werelds
 | 
| moel@344 |      9 |   Copyright (C) 2011 Roland Reinl <roland-reinl@gmx.de>
 | 
| moel@344 |     10 | 	
 | 
| moel@324 |     11 | */
 | 
| moel@324 |     12 | 
 | 
| moel@324 |     13 | using System;
 | 
| moel@324 |     14 | using System.Collections.Generic;
 | 
| moel@324 |     15 | using System.Globalization;
 | 
| moel@369 |     16 | using System.IO;
 | 
| moel@324 |     17 | using System.Text;
 | 
| moel@324 |     18 | using OpenHardwareMonitor.Collections;
 | 
| moel@324 |     19 | 
 | 
| moel@324 |     20 | namespace OpenHardwareMonitor.Hardware.HDD {
 | 
| moel@324 |     21 |   internal abstract class AbstractHarddrive : Hardware {
 | 
| moel@324 |     22 | 
 | 
| moel@324 |     23 |     private const int UPDATE_DIVIDER = 30; // update only every 30s
 | 
| moel@324 |     24 | 
 | 
| moel@324 |     25 |     // array of all harddrive types, matching type is searched in this order
 | 
| moel@324 |     26 |     private static Type[] hddTypes = {       
 | 
| moel@324 |     27 |       typeof(SSDPlextor),
 | 
| moel@324 |     28 |       typeof(SSDIntel),
 | 
| moel@324 |     29 |       typeof(SSDSandforce),
 | 
| moel@324 |     30 |       typeof(SSDIndilinx),
 | 
| moel@328 |     31 |       typeof(SSDSamsung),
 | 
| moel@358 |     32 |       typeof(SSDMicron),
 | 
| moel@324 |     33 |       typeof(GenericHarddisk)
 | 
| moel@324 |     34 |     };
 | 
| moel@324 |     35 | 
 | 
| moel@325 |     36 |     private string firmwareRevision;
 | 
| moel@324 |     37 |     private readonly ISmart smart;
 | 
| moel@324 |     38 | 
 | 
| moel@324 |     39 |     private readonly IntPtr handle;
 | 
| moel@324 |     40 |     private readonly int index;
 | 
| moel@324 |     41 |     private int count;
 | 
| moel@324 |     42 | 
 | 
| moel@324 |     43 |     private IList<SmartAttribute> smartAttributes;
 | 
| moel@324 |     44 |     private IDictionary<SmartAttribute, Sensor> sensors;
 | 
| moel@324 |     45 | 
 | 
| moel@369 |     46 |     private DriveInfo[] driveInfos;
 | 
| moel@369 |     47 |     private Sensor usageSensor;
 | 
| moel@369 |     48 | 
 | 
| moel@325 |     49 |     protected AbstractHarddrive(ISmart smart, string name, 
 | 
| moel@325 |     50 |       string firmwareRevision, int index, 
 | 
| moel@324 |     51 |       IEnumerable<SmartAttribute> smartAttributes, ISettings settings) 
 | 
| moel@324 |     52 |       : base(name, new Identifier("hdd",
 | 
| moel@324 |     53 |         index.ToString(CultureInfo.InvariantCulture)), settings)
 | 
| moel@324 |     54 |     {
 | 
| moel@325 |     55 |       this.firmwareRevision = firmwareRevision;
 | 
| moel@324 |     56 |       this.smart = smart;
 | 
| moel@324 |     57 |       handle = smart.OpenDrive(index);
 | 
| moel@324 |     58 | 
 | 
| moel@403 |     59 |       if (handle != smart.InvalidHandle)
 | 
| moel@403 |     60 |         smart.EnableSmart(handle, index);
 | 
| moel@324 |     61 | 
 | 
| moel@324 |     62 |       this.index = index;
 | 
| moel@324 |     63 |       this.count = 0;
 | 
| moel@324 |     64 | 
 | 
| moel@324 |     65 |       this.smartAttributes = new List<SmartAttribute>(smartAttributes);
 | 
| moel@324 |     66 | 
 | 
| moel@369 |     67 |       string[] logicalDrives = smart.GetLogicalDrives(index);
 | 
| moel@369 |     68 |       List<DriveInfo> driveInfoList = new List<DriveInfo>(logicalDrives.Length);
 | 
| moel@369 |     69 |       foreach (string logicalDrive in logicalDrives) {
 | 
| moel@369 |     70 |         try {
 | 
| moel@369 |     71 |           DriveInfo di = new DriveInfo(logicalDrive);
 | 
| moel@409 |     72 |           if (di.TotalSize > 0)
 | 
| moel@369 |     73 |             driveInfoList.Add(new DriveInfo(logicalDrive));
 | 
| moel@409 |     74 |         } catch (ArgumentException) {
 | 
| moel@409 |     75 |         } catch (IOException) {
 | 
| moel@409 |     76 |         } catch (UnauthorizedAccessException) {
 | 
| moel@409 |     77 |         }
 | 
| moel@369 |     78 |       }
 | 
| moel@369 |     79 |       driveInfos = driveInfoList.ToArray();
 | 
| moel@369 |     80 | 
 | 
| moel@324 |     81 |       CreateSensors();
 | 
| moel@324 |     82 |     }
 | 
| moel@324 |     83 | 
 | 
| moel@324 |     84 |     public static AbstractHarddrive CreateInstance(ISmart smart, 
 | 
| moel@324 |     85 |       int driveIndex, ISettings settings) 
 | 
| moel@324 |     86 |     {
 | 
| moel@324 |     87 |       IntPtr deviceHandle = smart.OpenDrive(driveIndex);
 | 
| moel@324 |     88 | 
 | 
| moel@403 |     89 |       string name = null;
 | 
| moel@403 |     90 |       string firmwareRevision = null;
 | 
| moel@403 |     91 |       DriveAttributeValue[] values = { };
 | 
| moel@324 |     92 | 
 | 
| moel@403 |     93 |       if (deviceHandle != smart.InvalidHandle) {
 | 
| moel@403 |     94 |         bool nameValid = smart.ReadNameAndFirmwareRevision(deviceHandle,
 | 
| moel@325 |     95 |         driveIndex, out name, out firmwareRevision);
 | 
| moel@403 |     96 |         bool smartEnabled = smart.EnableSmart(deviceHandle, driveIndex);
 | 
| moel@324 |     97 | 
 | 
| moel@403 |     98 |         if (smartEnabled)
 | 
| moel@403 |     99 |           values = smart.ReadSmartData(deviceHandle, driveIndex);
 | 
| moel@324 |    100 | 
 | 
| moel@403 |    101 |         smart.CloseHandle(deviceHandle);
 | 
| moel@324 |    102 | 
 | 
| moel@403 |    103 |         if (!nameValid) {
 | 
| moel@403 |    104 |           name = null;
 | 
| moel@403 |    105 |           firmwareRevision = null;
 | 
| moel@403 |    106 |         }
 | 
| moel@403 |    107 |       } else {
 | 
| moel@403 |    108 |         string[] logicalDrives = smart.GetLogicalDrives(driveIndex);
 | 
| moel@403 |    109 |         if (logicalDrives == null || logicalDrives.Length == 0)
 | 
| moel@403 |    110 |           return null;
 | 
| moel@409 |    111 | 
 | 
| moel@409 |    112 |         bool hasNonZeroSizeDrive = false;
 | 
| moel@409 |    113 |         foreach (string logicalDrive in logicalDrives) {
 | 
| moel@409 |    114 |           try {
 | 
| moel@409 |    115 |             DriveInfo di = new DriveInfo(logicalDrive);
 | 
| moel@409 |    116 |             if (di.TotalSize > 0) {
 | 
| moel@409 |    117 |               hasNonZeroSizeDrive = true;
 | 
| moel@409 |    118 |               break;
 | 
| moel@409 |    119 |             }
 | 
| moel@409 |    120 |           } catch (ArgumentException) { 
 | 
| moel@409 |    121 |           } catch (IOException) { 
 | 
| moel@409 |    122 |           } catch (UnauthorizedAccessException) {
 | 
| moel@409 |    123 |           }
 | 
| moel@409 |    124 |         }
 | 
| moel@409 |    125 | 
 | 
| moel@409 |    126 |         if (!hasNonZeroSizeDrive)
 | 
| moel@409 |    127 |           return null;
 | 
| moel@403 |    128 |       }
 | 
| moel@403 |    129 | 
 | 
| moel@403 |    130 |       if (string.IsNullOrEmpty(name))
 | 
| moel@403 |    131 |         name = "Generic Hard Disk";
 | 
| moel@403 |    132 | 
 | 
| moel@403 |    133 |       if (string.IsNullOrEmpty(firmwareRevision))
 | 
| moel@403 |    134 |         firmwareRevision = "Unknown";
 | 
| moel@324 |    135 | 
 | 
| moel@324 |    136 |       foreach (Type type in hddTypes) {
 | 
| moel@324 |    137 |         // get the array of name prefixes for the current type
 | 
| moel@324 |    138 |         NamePrefixAttribute[] namePrefixes = type.GetCustomAttributes(
 | 
| moel@324 |    139 |           typeof(NamePrefixAttribute), true) as NamePrefixAttribute[];
 | 
| moel@324 |    140 | 
 | 
| moel@324 |    141 |         // get the array of the required SMART attributes for the current type
 | 
| moel@324 |    142 |         RequireSmartAttribute[] requiredAttributes = type.GetCustomAttributes(
 | 
| moel@324 |    143 |           typeof(RequireSmartAttribute), true) as RequireSmartAttribute[];
 | 
| moel@324 |    144 | 
 | 
| moel@324 |    145 |         // check if all required attributes are present
 | 
| moel@324 |    146 |         bool allRequiredAttributesFound = true;
 | 
| moel@324 |    147 |         foreach (var requireAttribute in requiredAttributes) {
 | 
| moel@324 |    148 |           bool adttributeFound = false;
 | 
| moel@324 |    149 |           foreach (DriveAttributeValue value in values) {
 | 
| moel@324 |    150 |             if (value.Identifier == requireAttribute.AttributeId) {
 | 
| moel@324 |    151 |               adttributeFound = true;
 | 
| moel@324 |    152 |               break;
 | 
| moel@324 |    153 |             }
 | 
| moel@324 |    154 |           }
 | 
| moel@324 |    155 |           if (!adttributeFound) {
 | 
| moel@324 |    156 |             allRequiredAttributesFound = false;
 | 
| moel@324 |    157 |             break;
 | 
| moel@324 |    158 |           }
 | 
| moel@324 |    159 |         }
 | 
| moel@324 |    160 | 
 | 
| moel@324 |    161 |         // if an attribute is missing, then try the next type
 | 
| moel@324 |    162 |         if (!allRequiredAttributesFound)
 | 
| moel@324 |    163 |           continue;        
 | 
| moel@324 |    164 | 
 | 
| moel@324 |    165 |         // check if there is a matching name prefix for this type
 | 
| moel@324 |    166 |         foreach (NamePrefixAttribute prefix in namePrefixes) {
 | 
| moel@324 |    167 |           if (name.StartsWith(prefix.Prefix, StringComparison.InvariantCulture)) 
 | 
| moel@325 |    168 |             return Activator.CreateInstance(type, smart, name, firmwareRevision,
 | 
| moel@325 |    169 |               driveIndex, settings) as AbstractHarddrive;
 | 
| moel@324 |    170 |         }
 | 
| moel@324 |    171 |       }
 | 
| moel@324 |    172 | 
 | 
| moel@324 |    173 |       // no matching type has been found
 | 
| moel@324 |    174 |       return null;
 | 
| moel@324 |    175 |     }
 | 
| moel@324 |    176 | 
 | 
| moel@324 |    177 |     private void CreateSensors() {
 | 
| moel@324 |    178 |       sensors = new Dictionary<SmartAttribute, Sensor>();
 | 
| moel@324 |    179 | 
 | 
| moel@403 |    180 |       if (handle != smart.InvalidHandle) {
 | 
| moel@403 |    181 |         IList<Pair<SensorType, int>> sensorTypeAndChannels =
 | 
| moel@403 |    182 |           new List<Pair<SensorType, int>>();
 | 
| moel@324 |    183 | 
 | 
| moel@403 |    184 |         DriveAttributeValue[] values = smart.ReadSmartData(handle, index);
 | 
| moel@324 |    185 | 
 | 
| moel@403 |    186 |         foreach (SmartAttribute attribute in smartAttributes) {
 | 
| moel@403 |    187 |           if (!attribute.SensorType.HasValue)
 | 
| moel@403 |    188 |             continue;
 | 
| moel@324 |    189 | 
 | 
| moel@403 |    190 |           bool found = false;
 | 
| moel@403 |    191 |           foreach (DriveAttributeValue value in values) {
 | 
| moel@403 |    192 |             if (value.Identifier == attribute.Identifier) {
 | 
| moel@403 |    193 |               found = true;
 | 
| moel@403 |    194 |               break;
 | 
| moel@403 |    195 |             }
 | 
| moel@403 |    196 |           }
 | 
| moel@403 |    197 |           if (!found)
 | 
| moel@403 |    198 |             continue;
 | 
| moel@403 |    199 | 
 | 
| moel@403 |    200 |           Pair<SensorType, int> pair = new Pair<SensorType, int>(
 | 
| moel@403 |    201 |             attribute.SensorType.Value, attribute.SensorChannel);
 | 
| moel@403 |    202 | 
 | 
| moel@403 |    203 |           if (!sensorTypeAndChannels.Contains(pair)) {
 | 
| moel@403 |    204 |             Sensor sensor = new Sensor(attribute.Name,
 | 
| moel@403 |    205 |               attribute.SensorChannel, attribute.DefaultHiddenSensor,
 | 
| moel@403 |    206 |               attribute.SensorType.Value, this, attribute.ParameterDescriptions,
 | 
| moel@403 |    207 |               settings);
 | 
| moel@403 |    208 | 
 | 
| moel@403 |    209 |             sensors.Add(attribute, sensor);
 | 
| moel@403 |    210 |             ActivateSensor(sensor);
 | 
| moel@403 |    211 |             sensorTypeAndChannels.Add(pair);
 | 
| moel@324 |    212 |           }
 | 
| moel@324 |    213 |         }
 | 
| moel@324 |    214 |       }
 | 
| moel@369 |    215 | 
 | 
| moel@369 |    216 |       if (driveInfos.Length > 0) {
 | 
| moel@369 |    217 |         usageSensor = 
 | 
| moel@369 |    218 |           new Sensor("Used Space", 0, SensorType.Load, this, settings);
 | 
| moel@369 |    219 |         ActivateSensor(usageSensor);
 | 
| moel@369 |    220 |       }
 | 
| moel@324 |    221 |     }
 | 
| moel@324 |    222 | 
 | 
| moel@324 |    223 |     public override HardwareType HardwareType {
 | 
| moel@324 |    224 |       get { return HardwareType.HDD; }
 | 
| moel@324 |    225 |     }
 | 
| moel@324 |    226 | 
 | 
| moel@339 |    227 |     public virtual void UpdateAdditionalSensors(DriveAttributeValue[] values) {}
 | 
| moel@324 |    228 | 
 | 
| moel@324 |    229 |     public override void Update() {
 | 
| moel@324 |    230 |       if (count == 0) {
 | 
| moel@403 |    231 |         if (handle != smart.InvalidHandle) {
 | 
| moel@403 |    232 |           DriveAttributeValue[] values = smart.ReadSmartData(handle, index);
 | 
| moel@324 |    233 | 
 | 
| moel@403 |    234 |           foreach (KeyValuePair<SmartAttribute, Sensor> keyValuePair in sensors) 
 | 
| moel@403 |    235 |           {
 | 
| moel@403 |    236 |             SmartAttribute attribute = keyValuePair.Key;
 | 
| moel@403 |    237 |             foreach (DriveAttributeValue value in values) {
 | 
| moel@403 |    238 |               if (value.Identifier == attribute.Identifier) {
 | 
| moel@403 |    239 |                 Sensor sensor = keyValuePair.Value;
 | 
| moel@403 |    240 |                 sensor.Value = attribute.ConvertValue(value, sensor.Parameters);
 | 
| moel@403 |    241 |               }
 | 
| moel@324 |    242 |             }
 | 
| moel@324 |    243 |           }
 | 
| moel@403 |    244 | 
 | 
| moel@403 |    245 |           UpdateAdditionalSensors(values);
 | 
| moel@339 |    246 |         }
 | 
| moel@339 |    247 | 
 | 
| moel@369 |    248 |         if (usageSensor != null) {
 | 
| moel@369 |    249 |           long totalSize = 0;
 | 
| moel@369 |    250 |           long totalFreeSpace = 0;
 | 
| moel@385 |    251 | 
 | 
| moel@369 |    252 |           for (int i = 0; i < driveInfos.Length; i++) {
 | 
| moel@385 |    253 |             if (!driveInfos[i].IsReady)
 | 
| moel@385 |    254 |               continue;
 | 
| moel@385 |    255 |             try {
 | 
| moel@385 |    256 |               totalSize += driveInfos[i].TotalSize;
 | 
| moel@385 |    257 |               totalFreeSpace += driveInfos[i].TotalFreeSpace;
 | 
| moel@385 |    258 |             } catch (IOException) { } catch (UnauthorizedAccessException) { }
 | 
| moel@369 |    259 |           }
 | 
| moel@385 |    260 |           if (totalSize > 0) {
 | 
| moel@385 |    261 |             usageSensor.Value = 100.0f - (100.0f * totalFreeSpace) / totalSize;
 | 
| moel@385 |    262 |           } else {
 | 
| moel@385 |    263 |             usageSensor.Value = null;
 | 
| moel@385 |    264 |           }
 | 
| moel@369 |    265 |         }
 | 
| moel@324 |    266 |       }
 | 
| moel@324 |    267 | 
 | 
| moel@324 |    268 |       count++; 
 | 
| moel@324 |    269 |       count %= UPDATE_DIVIDER; 
 | 
| moel@324 |    270 |     }
 | 
| moel@324 |    271 | 
 | 
| moel@324 |    272 |     public override string GetReport() {
 | 
| moel@324 |    273 |       StringBuilder r = new StringBuilder();
 | 
| moel@324 |    274 | 
 | 
| moel@403 |    275 |       r.AppendLine(this.GetType().Name);
 | 
| moel@403 |    276 |       r.AppendLine();
 | 
| moel@403 |    277 |       r.AppendLine("Drive name: " + name);
 | 
| moel@403 |    278 |       r.AppendLine("Firmware version: " + firmwareRevision);
 | 
| moel@403 |    279 |       r.AppendLine();
 | 
| moel@324 |    280 | 
 | 
| moel@403 |    281 |       if (handle != smart.InvalidHandle) {
 | 
| moel@403 |    282 |         DriveAttributeValue[] values = smart.ReadSmartData(handle, index);
 | 
| moel@403 |    283 |         DriveThresholdValue[] thresholds =
 | 
| moel@403 |    284 |           smart.ReadSmartThresholds(handle, index);
 | 
| moel@324 |    285 | 
 | 
| moel@403 |    286 |         if (values.Length > 0) {
 | 
| moel@403 |    287 |           r.AppendFormat(CultureInfo.InvariantCulture,
 | 
| moel@403 |    288 |             " {0}{1}{2}{3}{4}{5}{6}{7}",
 | 
| moel@403 |    289 |             ("ID").PadRight(3),
 | 
| moel@403 |    290 |             ("Description").PadRight(35),
 | 
| moel@403 |    291 |             ("Raw Value").PadRight(13),
 | 
| moel@403 |    292 |             ("Worst").PadRight(6),
 | 
| moel@403 |    293 |             ("Value").PadRight(6),
 | 
| moel@403 |    294 |             ("Thres").PadRight(6),
 | 
| moel@403 |    295 |             ("Physical").PadRight(8),
 | 
| moel@403 |    296 |             Environment.NewLine);
 | 
| moel@403 |    297 | 
 | 
| moel@403 |    298 |           foreach (DriveAttributeValue value in values) {
 | 
| moel@403 |    299 |             if (value.Identifier == 0x00)
 | 
| moel@403 |    300 |               break;
 | 
| moel@403 |    301 | 
 | 
| moel@403 |    302 |             byte? threshold = null;
 | 
| moel@403 |    303 |             foreach (DriveThresholdValue t in thresholds) {
 | 
| moel@403 |    304 |               if (t.Identifier == value.Identifier) {
 | 
| moel@403 |    305 |                 threshold = t.Threshold;
 | 
| moel@403 |    306 |               }
 | 
| moel@324 |    307 |             }
 | 
| moel@403 |    308 | 
 | 
| moel@403 |    309 |             string description = "Unknown";
 | 
| moel@403 |    310 |             float? physical = null;
 | 
| moel@403 |    311 |             foreach (SmartAttribute a in smartAttributes) {
 | 
| moel@403 |    312 |               if (a.Identifier == value.Identifier) {
 | 
| moel@403 |    313 |                 description = a.Name;
 | 
| moel@403 |    314 |                 if (a.HasRawValueConversion | a.SensorType.HasValue)
 | 
| moel@403 |    315 |                   physical = a.ConvertValue(value, null);
 | 
| moel@403 |    316 |                 else
 | 
| moel@403 |    317 |                   physical = null;
 | 
| moel@403 |    318 |               }
 | 
| moel@403 |    319 |             }
 | 
| moel@403 |    320 | 
 | 
| moel@403 |    321 |             string raw = BitConverter.ToString(value.RawValue);
 | 
| moel@403 |    322 |             r.AppendFormat(CultureInfo.InvariantCulture,
 | 
| moel@403 |    323 |               " {0}{1}{2}{3}{4}{5}{6}{7}",
 | 
| moel@403 |    324 |               value.Identifier.ToString("X2").PadRight(3),
 | 
| moel@403 |    325 |               description.PadRight(35),
 | 
| moel@403 |    326 |               raw.Replace("-", "").PadRight(13),
 | 
| moel@403 |    327 |               value.WorstValue.ToString(CultureInfo.InvariantCulture).PadRight(6),
 | 
| moel@403 |    328 |               value.AttrValue.ToString(CultureInfo.InvariantCulture).PadRight(6),
 | 
| moel@403 |    329 |               (threshold.HasValue ? threshold.Value.ToString(
 | 
| moel@403 |    330 |                 CultureInfo.InvariantCulture) : "-").PadRight(6),
 | 
| moel@403 |    331 |               (physical.HasValue ? physical.Value.ToString(
 | 
| moel@403 |    332 |                 CultureInfo.InvariantCulture) : "-").PadRight(8),
 | 
| moel@403 |    333 |               Environment.NewLine);
 | 
| moel@324 |    334 |           }
 | 
| moel@403 |    335 |           r.AppendLine();
 | 
| moel@324 |    336 |         }
 | 
| moel@324 |    337 |       }
 | 
| moel@324 |    338 | 
 | 
| moel@369 |    339 |       foreach (DriveInfo di in driveInfos) {
 | 
| moel@404 |    340 |         if (!di.IsReady)
 | 
| moel@404 |    341 |           continue;
 | 
| moel@404 |    342 |         try {
 | 
| moel@404 |    343 |           r.AppendLine("Logical drive name: " + di.Name);
 | 
| moel@404 |    344 |           r.AppendLine("Format: " + di.DriveFormat);
 | 
| moel@404 |    345 |           r.AppendLine("Total size: " + di.TotalSize);
 | 
| moel@404 |    346 |           r.AppendLine("Total free space: " + di.TotalFreeSpace);
 | 
| moel@404 |    347 |           r.AppendLine();
 | 
| moel@404 |    348 |         } catch (IOException) { } catch (UnauthorizedAccessException) { }
 | 
| moel@369 |    349 |       }
 | 
| moel@369 |    350 | 
 | 
| moel@324 |    351 |       return r.ToString();
 | 
| moel@324 |    352 |     }
 | 
| moel@324 |    353 | 
 | 
| moel@374 |    354 |     protected static float RawToInt(byte[] raw, byte value,
 | 
| moel@374 |    355 |       IReadOnlyArray<IParameter> parameters) 
 | 
| moel@374 |    356 |     {
 | 
| moel@324 |    357 |       return (raw[3] << 24) | (raw[2] << 16) | (raw[1] << 8) | raw[0];
 | 
| moel@324 |    358 |     }
 | 
| moel@324 |    359 | 
 | 
| moel@324 |    360 |     public override void Close() {
 | 
| moel@403 |    361 |       if (handle != smart.InvalidHandle)
 | 
| moel@403 |    362 |         smart.CloseHandle(handle);
 | 
| moel@403 |    363 | 
 | 
| moel@324 |    364 |       base.Close();
 | 
| moel@324 |    365 |     }
 | 
| moel@324 |    366 | 
 | 
| moel@324 |    367 |     public override void Traverse(IVisitor visitor) {
 | 
| moel@324 |    368 |       foreach (ISensor sensor in Sensors)
 | 
| moel@324 |    369 |         sensor.Accept(visitor);
 | 
| moel@324 |    370 |     }
 | 
| moel@324 |    371 |   }
 | 
| moel@324 |    372 | }
 |