Skip to content

Commit 9c0bb6c

Browse files
committed
Solved the issue with empty values in the scada_values.csv when using
Alessandro's concealment module. We changed the way scada stores the values received from the PLCs, now we create a Pandas dataframe and use previous readings in case we have empty cell values
1 parent c2f1e30 commit 9c0bb6c

File tree

1 file changed

+70
-41
lines changed

1 file changed

+70
-41
lines changed

dhalsim/python2/generic_scada.py

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from dhalsim import py3_logger
1717
import threading
18+
import pandas as pd
1819

1920

2021
class Error(Exception):
@@ -79,28 +80,38 @@ def __init__(self, intermediate_yaml_path):
7980
'server': scada_server
8081
}
8182

82-
self.plc_data = self.generate_plcs()
83-
self.saved_values = [['iteration', 'timestamp']]
83+
# Simple data has PLC tags, without the index (T101, 1) becomes T101
84+
self.plc_data, self.simple_plc_data = self.generate_plcs()
85+
#self.saved_values = [['iteration', 'timestamp']]
8486

8587
for PLC in self.intermediate_yaml['plcs']:
8688
if 'sensors' not in PLC:
8789
PLC['sensors'] = list()
8890

8991
if 'actuators' not in PLC:
9092
PLC['actuators'] = list()
91-
self.saved_values[0].extend(PLC['sensors'])
92-
self.saved_values[0].extend(PLC['actuators'])
93+
#self.saved_values[0].extend(PLC['sensors'])
94+
#self.saved_values[0].extend(PLC['actuators'])
9395

9496
self.update_cache_flag = False
9597
self.plcs_ready = False
9698

97-
self.previous_cache = {}
98-
for ip in self.plc_data:
99-
self.previous_cache[ip] = [0] * len(self.plc_data[ip])
100-
101-
self.cache = {}
102-
for ip in self.plc_data:
103-
self.cache[ip] = [0] * len(self.plc_data[ip])
99+
100+
#self.previous_cache = {}
101+
#for ip in self.plc_data:
102+
# self.previous_cache[ip] = [0] * len(self.plc_data[ip])
103+
104+
105+
#self.cache = {}
106+
#for ip in self.plc_data:
107+
# self.cache[ip] = [0] * len(self.plc_data[ip])
108+
109+
columns_list = ['iteration', 'timestamp']
110+
columns_list.extend(self.get_scada_tags())
111+
# self.cache = dict.fromkeys(self.get_scada_tags())
112+
113+
self.cache = pd.DataFrame(columns=columns_list)
114+
self.cache.loc[0] = 0
104115

105116
self.updated_plc = {}
106117

@@ -119,6 +130,24 @@ def do_super_construction(self, scada_protocol, state):
119130
"""
120131
super(GenericScada, self).__init__(name='scada', state=state, protocol=scada_protocol)
121132

133+
def get_scada_tags(self):
134+
aux_scada_tags = []
135+
for PLC in self.intermediate_yaml['plcs']:
136+
137+
# We were having ordering issues by adding it as a set. Probably could be done in a more pythonic way
138+
if 'sensors' in PLC:
139+
for sensor in PLC['sensors']:
140+
if sensor not in aux_scada_tags:
141+
aux_scada_tags.append(sensor)
142+
143+
if 'actuators' in PLC:
144+
for actuator in PLC['actuators']:
145+
if actuator not in aux_scada_tags:
146+
aux_scada_tags.append(actuator)
147+
148+
# self.logger.debug('SCADA tags: ' + str(aux_scada_tags))
149+
return aux_scada_tags
150+
122151
@staticmethod
123152
def generate_real_tags(plcs):
124153
"""
@@ -253,16 +282,20 @@ def write_output(self):
253282
"""
254283
Writes the csv output of the scada
255284
"""
256-
with self.output_path.open(mode='w') as output:
257-
writer = csv.writer(output)
258-
writer.writerows(self.saved_values)
285+
results = self.cache
286+
results.to_csv(self.output_path, index=False)
287+
288+
#with self.output_path.open(mode='w') as output:
289+
# writer = csv.writer(output)
290+
# writer.writerows(self.saved_values)
259291

260292
def generate_plcs(self):
261293
"""
262294
Generates a list of tuples, the first part being the ip of a PLC,
263295
and the second being a list of tags attached to that PLC.
264296
"""
265297
plcs = OrderedDict()
298+
plcs_simple_tags = OrderedDict()
266299

267300
for PLC in self.intermediate_yaml['plcs']:
268301
if 'sensors' not in PLC:
@@ -272,13 +305,18 @@ def generate_plcs(self):
272305
PLC['actuators'] = list()
273306

274307
tags = []
308+
simple_tags = []
275309

276310
tags.extend(self.generate_tags(PLC['sensors']))
277311
tags.extend(self.generate_tags(PLC['actuators']))
278312

313+
simple_tags.extend(PLC['sensors'])
314+
simple_tags.extend(PLC['actuators'])
315+
279316
plcs[PLC['public_ip']] = tags
317+
plcs_simple_tags[PLC['public_ip']] = simple_tags
280318

281-
return plcs
319+
return plcs, plcs_simple_tags
282320

283321
def get_master_clock(self):
284322
"""
@@ -299,15 +337,14 @@ def update_cache(self, lock, cache_update_time):
299337
"""
300338

301339
while self.update_cache_flag:
302-
for plc_ip in self.cache:
303-
try:
304-
values = self.receive_multiple(self.plc_data[plc_ip], plc_ip)
340+
for plc_ip in self.plc_data:
341+
try:
342+
values = self.receive_multiple(self.plc_data[plc_ip], plc_ip)
305343
values_float = [float(x) for x in values]
306-
with lock:
307-
self.cache[plc_ip] = values_float
308-
309-
if self.cache[plc_ip]:
310-
self.previous_cache[plc_ip] = self.cache[plc_ip]
344+
with lock:
345+
clock = int(self.get_master_clock())
346+
self.cache.loc[clock, self.simple_plc_data[plc_ip]] = values_float
347+
self.cache.loc[clock, 'iteration'] = clock
311348
self.updated_plc[plc_ip] = True
312349
except Exception as e:
313350
self.logger.error(
@@ -367,28 +404,20 @@ def main_loop(self, sleep=0.5, test_break=False):
367404
# self.logger.debug(f'Waiting for plcs to be updated, tick {retry}')
368405
time.sleep(self.PLC_UPDATE_TIMEOUT_TICK)
369406

370-
# self.logger.debug('Finished waiting')
371-
master_time = self.get_master_clock()
372-
results = [master_time, datetime.now()]
407+
# self.logger.debug('Finished waiting')
408+
master_time = datetime.now()
409+
clock = int(self.get_master_clock())
410+
self.cache.loc[clock, 'timestamp'] = master_time
373411

374-
for plc_ip in self.plc_data:
375-
with lock:
376-
if not self.cache[plc_ip]:
377-
self.logger.warn(f'Data for PLC {plc_ip} is empty!')
378-
self.logger.warn(f'Using {self.previous_cache[plc_ip]} as stale data')
379-
results.extend(self.previous_cache[plc_ip])
380-
else:
381-
results.extend(self.cache[plc_ip])
382-
383-
# self.logger.debug('SCADA iteration complete')
384-
self.saved_values.append(results)
385-
386-
for plc_ip in self.plc_data:
387-
self.updated_plc[plc_ip] = False
412+
for ip in self.plc_data:
413+
if self.cache.loc[clock, self.simple_plc_data[ip]].isnull().any():
414+
# If any PLC values are empty, use previous value
415+
self.cache.loc[clock, self.simple_plc_data[ip]] = self.cache.loc[clock-1, self.simple_plc_data[ip]]
416+
self.updated_plc[ip] = False
388417

389418
# Save scada_values.csv when needed
390419
if 'saving_interval' in self.intermediate_yaml and master_time != 0 and \
391-
master_time % self.intermediate_yaml['saving_interval'] == 0:
420+
master_time % self.intermediate_yaml['saving_interval'] == 0:
392421
self.write_output()
393422

394423
self.set_sync(3)

0 commit comments

Comments
 (0)