opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Reflection;
30 using System.Collections.Generic;
31 using OpenMetaverse;
32 using OpenSim.Framework;
33 using log4net;
34 using OpenSim.Region.Framework.Interfaces;
35 using OpenSim.Region.Framework.Scenes;
36 using OpenSim.Region.ScriptEngine.Shared;
37 using OpenSim.Region.ScriptEngine.Shared.Api;
38  
39 namespace OpenSim.Region.ScriptEngine.Shared.Api.Plugins
40 {
41 public class SensorRepeat
42 {
43 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
44  
45 /// <summary>
46 /// Used by one-off and repeated sensors
47 /// </summary>
48 public class SensorInfo
49 {
50 public uint localID;
51 public UUID itemID;
52 public double interval;
53 public DateTime next;
54  
55 public string name;
56 public UUID keyID;
57 public int type;
58 public double range;
59 public double arc;
60 public SceneObjectPart host;
61  
62 public SensorInfo Clone()
63 {
64 return (SensorInfo)this.MemberwiseClone();
65 }
66 }
67  
68 public AsyncCommandManager m_CmdManager;
69  
70 /// <summary>
71 /// Number of sensors active.
72 /// </summary>
73 public int SensorsCount
74 {
75 get
76 {
77 return SenseRepeaters.Count;
78 }
79 }
80  
81 public SensorRepeat(AsyncCommandManager CmdManager)
82 {
83 m_CmdManager = CmdManager;
84 maximumRange = CmdManager.m_ScriptEngine.Config.GetDouble("SensorMaxRange", 96.0d);
85 maximumToReturn = CmdManager.m_ScriptEngine.Config.GetInt("SensorMaxResults", 16);
86 m_npcModule = m_CmdManager.m_ScriptEngine.World.RequestModuleInterface<INPCModule>();
87 }
88  
89 private INPCModule m_npcModule;
90  
91 private Object SenseLock = new Object();
92  
93 private const int AGENT = 1;
94 private const int AGENT_BY_USERNAME = 0x10;
95 private const int NPC = 0x20;
96 private const int OS_NPC = 0x01000000;
97 private const int ACTIVE = 2;
98 private const int PASSIVE = 4;
99 private const int SCRIPTED = 8;
100  
101 private double maximumRange = 96.0;
102 private int maximumToReturn = 16;
103  
104 //
105 // Sensed entity
106 //
107 private class SensedEntity : IComparable
108 {
109 public SensedEntity(double detectedDistance, UUID detectedID)
110 {
111 distance = detectedDistance;
112 itemID = detectedID;
113 }
114 public int CompareTo(object obj)
115 {
116 if (!(obj is SensedEntity)) throw new InvalidOperationException();
117 SensedEntity ent = (SensedEntity)obj;
118 if (ent == null || ent.distance < distance) return 1;
119 if (ent.distance > distance) return -1;
120 return 0;
121 }
122 public UUID itemID;
123 public double distance;
124 }
125  
126 /// <summary>
127 /// Sensors to process.
128 /// </summary>
129 /// <remarks>
130 /// Do not add or remove sensors from this list directly. Instead, copy the list and substitute the updated
131 /// copy. This is to avoid locking the list for the duration of the sensor sweep, which increases the danger
132 /// of deadlocks with future code updates.
133 ///
134 /// Always lock SenseRepeatListLock when updating this list.
135 /// </remarks>
136 private List<SensorInfo> SenseRepeaters = new List<SensorInfo>();
137 private object SenseRepeatListLock = new object();
138  
139 public void SetSenseRepeatEvent(uint m_localID, UUID m_itemID,
140 string name, UUID keyID, int type, double range,
141 double arc, double sec, SceneObjectPart host)
142 {
143 // Always remove first, in case this is a re-set
144 UnSetSenseRepeaterEvents(m_localID, m_itemID);
145  
146 if (sec == 0) // Disabling timer
147 return;
148  
149 // Add to timer
150 SensorInfo ts = new SensorInfo();
151 ts.localID = m_localID;
152 ts.itemID = m_itemID;
153 ts.interval = sec;
154 ts.name = name;
155 ts.keyID = keyID;
156 ts.type = type;
157 if (range > maximumRange)
158 ts.range = maximumRange;
159 else
160 ts.range = range;
161 ts.arc = arc;
162 ts.host = host;
163  
164 ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval);
165  
166 AddSenseRepeater(ts);
167 }
168  
169 private void AddSenseRepeater(SensorInfo senseRepeater)
170 {
171 lock (SenseRepeatListLock)
172 {
173 List<SensorInfo> newSenseRepeaters = new List<SensorInfo>(SenseRepeaters);
174 newSenseRepeaters.Add(senseRepeater);
175 SenseRepeaters = newSenseRepeaters;
176 }
177 }
178  
179 public void UnSetSenseRepeaterEvents(uint m_localID, UUID m_itemID)
180 {
181 // Remove from timer
182 lock (SenseRepeatListLock)
183 {
184 List<SensorInfo> newSenseRepeaters = new List<SensorInfo>();
185 foreach (SensorInfo ts in SenseRepeaters)
186 {
187 if (ts.localID != m_localID || ts.itemID != m_itemID)
188 {
189 newSenseRepeaters.Add(ts);
190 }
191 }
192  
193 SenseRepeaters = newSenseRepeaters;
194 }
195 }
196  
197 public void CheckSenseRepeaterEvents()
198 {
199 // Go through all timers
200 foreach (SensorInfo ts in SenseRepeaters)
201 {
202 // Time has passed?
203 if (ts.next.ToUniversalTime() < DateTime.Now.ToUniversalTime())
204 {
205 SensorSweep(ts);
206 // set next interval
207 ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval);
208 }
209 }
210 }
211  
212 public void SenseOnce(uint m_localID, UUID m_itemID,
213 string name, UUID keyID, int type,
214 double range, double arc, SceneObjectPart host)
215 {
216 // Add to timer
217 SensorInfo ts = new SensorInfo();
218 ts.localID = m_localID;
219 ts.itemID = m_itemID;
220 ts.interval = 0;
221 ts.name = name;
222 ts.keyID = keyID;
223 ts.type = type;
224 if (range > maximumRange)
225 ts.range = maximumRange;
226 else
227 ts.range = range;
228 ts.arc = arc;
229 ts.host = host;
230 SensorSweep(ts);
231 }
232  
233 private void SensorSweep(SensorInfo ts)
234 {
235 if (ts.host == null)
236 {
237 return;
238 }
239  
240 List<SensedEntity> sensedEntities = new List<SensedEntity>();
241  
242 // Is the sensor type is AGENT and not SCRIPTED then include agents
243 if ((ts.type & (AGENT | AGENT_BY_USERNAME | NPC | OS_NPC)) != 0 && (ts.type & SCRIPTED) == 0)
244 {
245 sensedEntities.AddRange(doAgentSensor(ts));
246 }
247  
248 // If SCRIPTED or PASSIVE or ACTIVE check objects
249 if ((ts.type & SCRIPTED) != 0 || (ts.type & PASSIVE) != 0 || (ts.type & ACTIVE) != 0)
250 {
251 sensedEntities.AddRange(doObjectSensor(ts));
252 }
253  
254 lock (SenseLock)
255 {
256 if (sensedEntities.Count == 0)
257 {
258 // send a "no_sensor"
259 // Add it to queue
260 m_CmdManager.m_ScriptEngine.PostScriptEvent(ts.itemID,
261 new EventParams("no_sensor", new Object[0],
262 new DetectParams[0]));
263 }
264 else
265 {
266 // Sort the list to get everything ordered by distance
267 sensedEntities.Sort();
268 int count = sensedEntities.Count;
269 int idx;
270 List<DetectParams> detected = new List<DetectParams>();
271 for (idx = 0; idx < count; idx++)
272 {
273 try
274 {
275 DetectParams detect = new DetectParams();
276 detect.Key = sensedEntities[idx].itemID;
277 detect.Populate(m_CmdManager.m_ScriptEngine.World);
278 detected.Add(detect);
279 }
280 catch (Exception)
281 {
282 // Ignore errors, the object has been deleted or the avatar has gone and
283 // there was a problem in detect.Populate so nothing added to the list
284 }
285 if (detected.Count == maximumToReturn)
286 break;
287 }
288  
289 if (detected.Count == 0)
290 {
291 // To get here with zero in the list there must have been some sort of problem
292 // like the object being deleted or the avatar leaving to have caused some
293 // difficulty during the Populate above so fire a no_sensor event
294 m_CmdManager.m_ScriptEngine.PostScriptEvent(ts.itemID,
295 new EventParams("no_sensor", new Object[0],
296 new DetectParams[0]));
297 }
298 else
299 {
300 m_CmdManager.m_ScriptEngine.PostScriptEvent(ts.itemID,
301 new EventParams("sensor",
302 new Object[] {new LSL_Types.LSLInteger(detected.Count) },
303 detected.ToArray()));
304 }
305 }
306 }
307 }
308  
309 private List<SensedEntity> doObjectSensor(SensorInfo ts)
310 {
311 List<EntityBase> Entities;
312 List<SensedEntity> sensedEntities = new List<SensedEntity>();
313  
314 // If this is an object sense by key try to get it directly
315 // rather than getting a list to scan through
316 if (ts.keyID != UUID.Zero)
317 {
318 EntityBase e = null;
319 m_CmdManager.m_ScriptEngine.World.Entities.TryGetValue(ts.keyID, out e);
320 if (e == null)
321 return sensedEntities;
322 Entities = new List<EntityBase>();
323 Entities.Add(e);
324 }
325 else
326 {
327 Entities = new List<EntityBase>(m_CmdManager.m_ScriptEngine.World.GetEntities());
328 }
329 SceneObjectPart SensePoint = ts.host;
330  
331 Vector3 fromRegionPos = SensePoint.GetWorldPosition();
332  
333 // pre define some things to avoid repeated definitions in the loop body
334 Vector3 toRegionPos;
335 double dis;
336 int objtype;
337 SceneObjectPart part;
338 float dx;
339 float dy;
340 float dz;
341  
342 Quaternion q = SensePoint.GetWorldRotation();
343 if (SensePoint.ParentGroup.IsAttachment)
344 {
345 // In attachments, rotate the sensor cone with the
346 // avatar rotation. This may include a nonzero elevation if
347 // in mouselook.
348 // This will not include the rotation and position of the
349 // attachment point (e.g. your head when a sensor is in your
350 // hair attached to your scull. Your hair will turn with
351 // your head but the sensor will stay with your (global)
352 // avatar rotation and position.
353 // Position of a sensor in a child prim attached to an avatar
354 // will be still wrong.
355 ScenePresence avatar = m_CmdManager.m_ScriptEngine.World.GetScenePresence(SensePoint.ParentGroup.AttachedAvatar);
356  
357 // Don't proceed if the avatar for this attachment has since been removed from the scene.
358 if (avatar == null)
359 return sensedEntities;
360  
361 q = avatar.GetWorldRotation() * q;
362 }
363  
364 LSL_Types.Quaternion r = new LSL_Types.Quaternion(q);
365 LSL_Types.Vector3 forward_dir = (new LSL_Types.Vector3(1, 0, 0) * r);
366 double mag_fwd = LSL_Types.Vector3.Mag(forward_dir);
367  
368 Vector3 ZeroVector = new Vector3(0, 0, 0);
369  
370 bool nameSearch = !string.IsNullOrEmpty(ts.name);
371  
372 foreach (EntityBase ent in Entities)
373 {
374 bool keep = true;
375  
376 if (nameSearch && ent.Name != ts.name) // Wrong name and it is a named search
377 continue;
378  
379 if (ent.IsDeleted) // taken so long to do this it has gone from the scene
380 continue;
381  
382 if (!(ent is SceneObjectGroup)) // dont bother if it is a pesky avatar
383 continue;
384 toRegionPos = ent.AbsolutePosition;
385  
386 // Calculation is in line for speed
387 dx = toRegionPos.X - fromRegionPos.X;
388 dy = toRegionPos.Y - fromRegionPos.Y;
389 dz = toRegionPos.Z - fromRegionPos.Z;
390  
391 // Weed out those that will not fit in a cube the size of the range
392 // no point calculating if they are within a sphere the size of the range
393 // if they arent even in the cube
394 if (Math.Abs(dx) > ts.range || Math.Abs(dy) > ts.range || Math.Abs(dz) > ts.range)
395 dis = ts.range + 1.0;
396 else
397 dis = Math.Sqrt(dx * dx + dy * dy + dz * dz);
398  
399 if (keep && dis <= ts.range && ts.host.UUID != ent.UUID)
400 {
401 // In Range and not the object containing the script, is it the right Type ?
402 objtype = 0;
403  
404 part = ((SceneObjectGroup)ent).RootPart;
405 if (part.ParentGroup.AttachmentPoint != 0) // Attached so ignore
406 continue;
407  
408 if (part.Inventory.ContainsScripts())
409 {
410 objtype |= ACTIVE | SCRIPTED; // Scripted and active. It COULD have one hidden ...
411 }
412 else
413 {
414 if (ent.Velocity.Equals(ZeroVector))
415 {
416 objtype |= PASSIVE; // Passive non-moving
417 }
418 else
419 {
420 objtype |= ACTIVE; // moving so active
421 }
422 }
423  
424 // If any of the objects attributes match any in the requested scan type
425 if (((ts.type & objtype) != 0))
426 {
427 // Right type too, what about the other params , key and name ?
428 if (ts.arc < Math.PI)
429 {
430 // not omni-directional. Can you see it ?
431 // vec forward_dir = llRot2Fwd(llGetRot())
432 // vec obj_dir = toRegionPos-fromRegionPos
433 // dot=dot(forward_dir,obj_dir)
434 // mag_fwd = mag(forward_dir)
435 // mag_obj = mag(obj_dir)
436 // ang = acos(dot /(mag_fwd*mag_obj))
437 double ang_obj = 0;
438 try
439 {
440 Vector3 diff = toRegionPos - fromRegionPos;
441 double dot = LSL_Types.Vector3.Dot(forward_dir, diff);
442 double mag_obj = LSL_Types.Vector3.Mag(diff);
443 ang_obj = Math.Acos(dot / (mag_fwd * mag_obj));
444 }
445 catch
446 {
447 }
448  
449 if (ang_obj > ts.arc) keep = false;
450 }
451  
452 if (keep == true)
453 {
454 // add distance for sorting purposes later
455 sensedEntities.Add(new SensedEntity(dis, ent.UUID));
456 }
457 }
458 }
459 }
460 return sensedEntities;
461 }
462  
463 private List<SensedEntity> doAgentSensor(SensorInfo ts)
464 {
465 List<SensedEntity> sensedEntities = new List<SensedEntity>();
466  
467 // If nobody about quit fast
468 if (m_CmdManager.m_ScriptEngine.World.GetRootAgentCount() == 0)
469 return sensedEntities;
470  
471 SceneObjectPart SensePoint = ts.host;
472 Vector3 fromRegionPos = SensePoint.GetWorldPosition();
473  
474 Quaternion q = SensePoint.GetWorldRotation();
475 if (SensePoint.ParentGroup.IsAttachment)
476 {
477 // In attachments, rotate the sensor cone with the
478 // avatar rotation. This may include a nonzero elevation if
479 // in mouselook.
480 // This will not include the rotation and position of the
481 // attachment point (e.g. your head when a sensor is in your
482 // hair attached to your scull. Your hair will turn with
483 // your head but the sensor will stay with your (global)
484 // avatar rotation and position.
485 // Position of a sensor in a child prim attached to an avatar
486 // will be still wrong.
487 ScenePresence avatar = m_CmdManager.m_ScriptEngine.World.GetScenePresence(SensePoint.ParentGroup.AttachedAvatar);
488  
489 // Don't proceed if the avatar for this attachment has since been removed from the scene.
490 if (avatar == null)
491 return sensedEntities;
492  
493 q = avatar.GetWorldRotation() * q;
494 }
495  
496 LSL_Types.Quaternion r = new LSL_Types.Quaternion(q);
497 LSL_Types.Vector3 forward_dir = (new LSL_Types.Vector3(1, 0, 0) * r);
498 double mag_fwd = LSL_Types.Vector3.Mag(forward_dir);
499 bool attached = (SensePoint.ParentGroup.AttachmentPoint != 0);
500 Vector3 toRegionPos;
501 double dis;
502  
503 Action<ScenePresence> senseEntity = new Action<ScenePresence>(presence =>
504 {
505 // m_log.DebugFormat(
506 // "[SENSOR REPEAT]: Inspecting scene presence {0}, type {1} on sensor sweep for {2}, type {3}",
507 // presence.Name, presence.PresenceType, ts.name, ts.type);
508  
509 if ((ts.type & NPC) == 0 && (ts.type & OS_NPC) == 0 && presence.PresenceType == PresenceType.Npc)
510 {
511 INPC npcData = m_npcModule.GetNPC(presence.UUID, presence.Scene);
512 if (npcData == null || !npcData.SenseAsAgent)
513 {
514 // m_log.DebugFormat(
515 // "[SENSOR REPEAT]: Discarding NPC {0} from agent sense sweep for script item id {1}",
516 // presence.Name, ts.itemID);
517 return;
518 }
519 }
520  
521 if ((ts.type & AGENT) == 0)
522 {
523 if (presence.PresenceType == PresenceType.User)
524 {
525 return;
526 }
527 else
528 {
529 INPC npcData = m_npcModule.GetNPC(presence.UUID, presence.Scene);
530 if (npcData != null && npcData.SenseAsAgent)
531 {
532 // m_log.DebugFormat(
533 // "[SENSOR REPEAT]: Discarding NPC {0} from non-agent sense sweep for script item id {1}",
534 // presence.Name, ts.itemID);
535 return;
536 }
537 }
538 }
539  
540 if (presence.IsDeleted || presence.IsChildAgent || presence.GodLevel > 0.0)
541 return;
542  
543 // if the object the script is in is attached and the avatar is the owner
544 // then this one is not wanted
545 if (attached && presence.UUID == SensePoint.OwnerID)
546 return;
547  
548 toRegionPos = presence.AbsolutePosition;
549 dis = Math.Abs(Util.GetDistanceTo(toRegionPos, fromRegionPos));
550  
551 // Disabled for now since all osNpc* methods check for appropriate ownership permission.
552 // Perhaps could be re-enabled as an NPC setting at some point since being able to make NPCs not
553 // sensed might be useful.
554 // if (presence.PresenceType == PresenceType.Npc && npcModule != null)
555 // {
556 // UUID npcOwner = npcModule.GetOwner(presence.UUID);
557 // if (npcOwner != UUID.Zero && npcOwner != SensePoint.OwnerID)
558 // return;
559 // }
560  
561 // are they in range
562 if (dis <= ts.range)
563 {
564 // Are they in the required angle of view
565 if (ts.arc < Math.PI)
566 {
567 // not omni-directional. Can you see it ?
568 // vec forward_dir = llRot2Fwd(llGetRot())
569 // vec obj_dir = toRegionPos-fromRegionPos
570 // dot=dot(forward_dir,obj_dir)
571 // mag_fwd = mag(forward_dir)
572 // mag_obj = mag(obj_dir)
573 // ang = acos(dot /(mag_fwd*mag_obj))
574 double ang_obj = 0;
575 try
576 {
577 LSL_Types.Vector3 obj_dir = new LSL_Types.Vector3(
578 toRegionPos - fromRegionPos);
579 double dot = LSL_Types.Vector3.Dot(forward_dir, obj_dir);
580 double mag_obj = LSL_Types.Vector3.Mag(obj_dir);
581 ang_obj = Math.Acos(dot / (mag_fwd * mag_obj));
582 }
583 catch
584 {
585 }
586 if (ang_obj <= ts.arc)
587 {
588 sensedEntities.Add(new SensedEntity(dis, presence.UUID));
589 }
590 }
591 else
592 {
593 sensedEntities.Add(new SensedEntity(dis, presence.UUID));
594 }
595 }
596 });
597  
598 // If this is an avatar sense by key try to get them directly
599 // rather than getting a list to scan through
600 if (ts.keyID != UUID.Zero)
601 {
602 ScenePresence sp;
603 // Try direct lookup by UUID
604 if (!m_CmdManager.m_ScriptEngine.World.TryGetScenePresence(ts.keyID, out sp))
605 return sensedEntities;
606 senseEntity(sp);
607 }
608 else if (!string.IsNullOrEmpty(ts.name))
609 {
610 ScenePresence sp;
611 // Try lookup by name will return if/when found
612 if (((ts.type & AGENT) != 0) && m_CmdManager.m_ScriptEngine.World.TryGetAvatarByName(ts.name, out sp))
613 senseEntity(sp);
614 if ((ts.type & AGENT_BY_USERNAME) != 0)
615 {
616 m_CmdManager.m_ScriptEngine.World.ForEachRootScenePresence(
617 delegate (ScenePresence ssp)
618 {
619 if (ssp.Lastname == "Resident")
620 {
621 if (ssp.Firstname.ToLower() == ts.name)
622 senseEntity(ssp);
623 return;
624 }
625 if (ssp.Name.Replace(" ", ".").ToLower() == ts.name)
626 senseEntity(ssp);
627 }
628 );
629 }
630  
631 return sensedEntities;
632 }
633 else
634 {
635 m_CmdManager.m_ScriptEngine.World.ForEachRootScenePresence(senseEntity);
636 }
637 return sensedEntities;
638 }
639  
640 public Object[] GetSerializationData(UUID itemID)
641 {
642 List<Object> data = new List<Object>();
643  
644 foreach (SensorInfo ts in SenseRepeaters)
645 {
646 if (ts.itemID == itemID)
647 {
648 data.Add(ts.interval);
649 data.Add(ts.name);
650 data.Add(ts.keyID);
651 data.Add(ts.type);
652 data.Add(ts.range);
653 data.Add(ts.arc);
654 }
655 }
656  
657 return data.ToArray();
658 }
659  
660 public void CreateFromData(uint localID, UUID itemID, UUID objectID,
661 Object[] data)
662 {
663 SceneObjectPart part =
664 m_CmdManager.m_ScriptEngine.World.GetSceneObjectPart(
665 objectID);
666  
667 if (part == null)
668 return;
669  
670 int idx = 0;
671  
672 while (idx < data.Length)
673 {
674 SensorInfo ts = new SensorInfo();
675  
676 ts.localID = localID;
677 ts.itemID = itemID;
678  
679 ts.interval = (double)data[idx];
680 ts.name = (string)data[idx+1];
681 ts.keyID = (UUID)data[idx+2];
682 ts.type = (int)data[idx+3];
683 ts.range = (double)data[idx+4];
684 ts.arc = (double)data[idx+5];
685 ts.host = part;
686  
687 ts.next =
688 DateTime.Now.ToUniversalTime().AddSeconds(ts.interval);
689  
690 AddSenseRepeater(ts);
691  
692 idx += 6;
693 }
694 }
695  
696 public List<SensorInfo> GetSensorInfo()
697 {
698 List<SensorInfo> retList = new List<SensorInfo>();
699  
700 lock (SenseRepeatListLock)
701 {
702 foreach (SensorInfo i in SenseRepeaters)
703 retList.Add(i.Clone());
704 }
705  
706 return retList;
707 }
708 }
709 }