Skip to content

Commit e0f6348

Browse files
m-fehrNoahHastings
andcommitted
feat: parse DEVICE_ACT entries from SMV file (based on PR #101)
Adds Device.activation_times — a sorted list of (time, state) tuples recording when a device activated or deactivated during the simulation. Two bugs from PR #101 were fixed before merging: - time was stored as str instead of float - the first data field is a global device index (1-based across all devices), not a per-name group index; line devices with >1 entry would have caused an IndexError. Fixed by building a registration- order list during SMV parsing and looking up directly by global index. Co-Authored-By: NoahHastings <NoahHastings@users.noreply.github.com>
1 parent f9113e0 commit e0f6348

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

fdsreader/devc/device.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(
2525
self.position = position
2626
self.orientation = orientation
2727
self._data_callback = lambda: None
28+
self._activation_times: list[tuple[float, bool]] = []
2829

2930
@property
3031
def data(self):
@@ -51,6 +52,26 @@ def xyz(self):
5152
"""Alias for :class:`Device`.position."""
5253
return self.position
5354

55+
@property
56+
def activation_times(self) -> list[tuple[float, bool]]:
57+
"""List of ``(time, state)`` tuples recording when this device activated or deactivated.
58+
59+
Each entry is a ``(float, bool)`` tuple where *time* is the simulation time in seconds
60+
and *state* is ``True`` for activation and ``False`` for deactivation.
61+
The list is sorted by time.
62+
"""
63+
return self._activation_times
64+
65+
def add_activation_time(self, time: float, state: bool) -> None:
66+
"""Record an activation event for this device.
67+
68+
Args:
69+
time: Simulation time of the event in seconds.
70+
state: ``True`` if the device activated, ``False`` if it deactivated.
71+
"""
72+
self._activation_times.append((time, state))
73+
self._activation_times.sort(key=lambda a: a[0])
74+
5475
def clear_cache(self):
5576
"""Remove all data from the internal cache that has been loaded so far to free memory."""
5677
if hasattr(self, "_data"):

fdsreader/simulation.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ def __init__(self, path: str):
217217
pickle.dump(self, open(Simulation._get_pickle_filename(self.root_path, self.chid), "wb"), protocol=4)
218218

219219
def parse_smv_file(self):
220+
# Global device list in registration order — used to resolve DEVICE_ACT indices.
221+
_devices_by_global_index: list[Device] = []
220222
with open(self.smv_file_path) as smv_file:
221223
for line in smv_file:
222224
keyword = line.strip()
@@ -265,6 +267,14 @@ def parse_smv_file(self):
265267
self._devices[device_id] = [self._devices[device_id], device]
266268
else:
267269
self._devices[device_id] = device
270+
_devices_by_global_index.append(device)
271+
elif keyword.startswith("DEVICE_ACT"):
272+
idx_str, time_str, value_str = smv_file.readline().split()
273+
global_index = int(idx_str) - 1 # FDS uses 1-based global device index
274+
act_time = float(time_str)
275+
act_state = bool(int(value_str))
276+
if global_index < len(_devices_by_global_index):
277+
_devices_by_global_index[global_index].add_activation_time(act_time, act_state)
268278
elif keyword.startswith("SLC"):
269279
self._load_slice(smv_file, keyword)
270280
elif keyword.startswith("BNDS"):

tests/acceptance_tests/test_devc.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,18 @@ def test_clear_cache_with_line_devices():
4141
sim = Simulation(os.path.join(TEST_DIR, "../cases/devc_data"))
4242
assert any(isinstance(d, list) for d in sim.devices), "Test data should contain line devices"
4343
sim.clear_cache()
44+
45+
46+
def test_activation_times_type(devc_sim):
47+
"""activation_times entries must be (float, bool) tuples (PR #101)."""
48+
for entry in devc_sim.devices["TC_Room"]:
49+
for t, s in entry.activation_times:
50+
assert isinstance(t, float), f"Expected float time, got {type(t)}"
51+
assert isinstance(s, bool), f"Expected bool state, got {type(s)}"
52+
53+
54+
def test_activation_times_monotonic(devc_sim):
55+
"""activation_times must be sorted by time (PR #101)."""
56+
for entry in devc_sim.devices["TC_Room"]:
57+
times = [t for t, _ in entry.activation_times]
58+
assert times == sorted(times), "activation_times not sorted by time"

0 commit comments

Comments
 (0)