1515
1616from dhalsim import py3_logger
1717import threading
18+ import pandas as pd
1819
1920
2021class 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