1818from modbus_simulator .utils .backgroundJob import BackgroundJob
1919import re
2020import os
21+ from json import load , dump
2122from kivy .config import Config
2223from kivy .lang import Builder
2324import modbus_simulator .ui .datamodel
3031 'input registers' : 'input_registers' ,
3132 'holding registers' : 'holding_registers'
3233}
34+
3335settings_icon = resource_filename (__name__ , "assets/Control-Panel.png" )
3436app_icon = resource_filename (__name__ , "assets/riptideLogo.png" )
3537modbus_template = resource_filename (__name__ , "templates/modbussimu.kv" )
3638Builder .load_file (modbus_template )
3739
40+ SLAVES_FILE = resource_filename (__name__ , "slaves.json" )
41+
3842
3943class FloatInput (TextInput ):
4044 pat2 = re .compile (r'\d+(?:,\d+)?' )
@@ -58,6 +62,9 @@ class Gui(BoxLayout):
5862 # Checkbox to select between tcp/serial
5963 interfaces = ObjectProperty ()
6064
65+ tcp = ObjectProperty ()
66+ serial = ObjectProperty ()
67+
6168 # Boxlayout to hold interface settings
6269 interface_settings = ObjectProperty ()
6370
@@ -93,6 +100,8 @@ class Gui(BoxLayout):
93100 settings = ObjectProperty ()
94101 riptide_logo = ObjectProperty ()
95102
103+ reset_sim_btn = ObjectProperty ()
104+
96105 # Helpers
97106 # slaves = ["%s" %i for i in xrange(1, 248)]
98107 _data_map = {"tcp" : {}, "rtu" : {}}
@@ -292,35 +301,41 @@ def _create_modbus_device(self):
292301
293302 def start_server (self , btn ):
294303 if btn .state == "down" :
295- self ._create_modbus_device ()
296-
297- self .modbus_device .start ()
298- self .server_running = True
299- self .interface_settings .disabled = True
300- self .interfaces .disabled = True
301- self .slave_pane .disabled = False
302- if len (self .slave_list .adapter .selection ):
303- self .data_model_loc .disabled = False
304- if self .simulating :
305- self ._simulate ()
306-
304+ self ._start_server ()
307305 btn .text = "Stop"
308-
309306 else :
310- self .simulating = False
311- self ._simulate ()
312- self .modbus_device .stop ()
313- self .server_running = False
314- self .interface_settings .disabled = False
315- self .interfaces .disabled = False
316- self .slave_pane .disabled = True
317- self .data_model_loc .disabled = True
307+ self ._stop_server ()
318308 btn .text = "Start"
319309
310+ def _start_server (self ):
311+ self ._create_modbus_device ()
312+
313+ self .modbus_device .start ()
314+ self .server_running = True
315+ self .interface_settings .disabled = True
316+ self .interfaces .disabled = True
317+ self .slave_pane .disabled = False
318+ if len (self .slave_list .adapter .selection ):
319+ self .data_model_loc .disabled = False
320+ if self .simulating :
321+ self ._simulate ()
322+
323+ def _stop_server (self ):
324+ self .simulating = False
325+ self ._simulate ()
326+ self .modbus_device .stop ()
327+ self .server_running = False
328+ self .interface_settings .disabled = False
329+ self .interfaces .disabled = False
330+ self .slave_pane .disabled = True
331+ self .data_model_loc .disabled = True
332+
320333 def update_tcp_connection_info (self , checkbox , value ):
321334 self .active_server = "tcp"
322335 if value :
323336 self .interface_settings .current = checkbox
337+ if self .last_active_port ['tcp' ] == "" :
338+ self .last_active_port ['tcp' ] = 5440
324339 self .port .text = self .last_active_port ['tcp' ]
325340 self ._restore ()
326341 else :
@@ -335,7 +350,6 @@ def update_serial_connection_info(self, checkbox, value):
335350 self .last_active_port ['serial' ] = '/dev/ptyp0'
336351 self .port .text = self .last_active_port ['serial' ]
337352 self ._restore ()
338-
339353 else :
340354 self .last_active_port ['serial' ] = self .port .text
341355 self ._backup ()
@@ -351,10 +365,14 @@ def add_slaves(self, *args):
351365 selected = self .slave_list .adapter .selection
352366 data = self .slave_list .adapter .data
353367 ret = self ._process_slave_data (data )
368+ self ._add_slaves (selected , data , ret )
369+
370+ def _add_slaves (self , selected , data , ret ):
354371 if ret [0 ]:
355372 start_slave_add , slave_count = ret [1 :]
356373 else :
357374 return
375+
358376 for slave_to_add in xrange (start_slave_add ,
359377 start_slave_add + slave_count ):
360378 if str (slave_to_add ) in self .data_map :
@@ -388,7 +406,7 @@ def add_slaves(self, *args):
388406 self .modbus_device .add_slave (slave_to_add )
389407 for block_name , block_type in BLOCK_TYPES .items ():
390408 self .modbus_device .add_block (slave_to_add ,
391- block_name , block_type , self .block_start , self .block_size )
409+ block_name , block_type , self .block_start , self .block_size )
392410
393411 data .append (str (slave_to_add ))
394412 self .slave_list .adapter .data = data
@@ -462,25 +480,32 @@ def delete_slaves(self, *args):
462480 self .data_map .pop (slave )
463481
464482 def update_data_models (self , * args ):
465- ct = self .data_models .current_tab
483+ active = self .active_slave
484+ tab = self .data_models .current_tab
485+ count = self .data_count .text
486+ self ._update_data_models (active , tab , count , 1 )
487+
488+ def _update_data_models (self , active , tab , count , value ):
489+ ct = tab
466490 current_tab = MAP [ct .text ]
467491
468492 ct .content .update_view ()
469493 # self.data_map[self.active_slave][current_tab]['dirty'] = False
470- _data = self .data_map [self . active_slave ][current_tab ]
494+ _data = self .data_map [active ][current_tab ]
471495 item_strings = _data ['item_strings' ]
472- for i in xrange (int (self . data_count . text )):
496+ for i in xrange (int (count )):
473497 if len (item_strings ) < self .block_size :
474- updated_data , item_strings = ct .content .add_data (1 , item_strings )
498+ _value = 1 if isinstance (value , int ) else value [i ]
499+ updated_data , item_strings = ct .content .add_data (_value , item_strings )
475500 _data ['data' ].update (updated_data )
476501 _data ['item_strings' ] = item_strings
477502 for k , v in updated_data .iteritems ():
478- self .modbus_device .set_values (int (self . active_slave ),
503+ self .modbus_device .set_values (int (active ),
479504 current_tab , k , v )
480505 else :
481506 msg = ("OutOfModbusBlockError: address %s"
482- " is out of block size %s" % (len (item_strings ),
483- self .block_size ))
507+ " is out of block size %s" % (len (item_strings ),
508+ self .block_size ))
484509 self .show_error (msg )
485510 break
486511
@@ -572,8 +597,10 @@ def change_datamodel_settings(self, key, value):
572597 def start_stop_simulation (self , btn ):
573598 if btn .state == "down" :
574599 self .simulating = True
600+ self .reset_sim_btn .disabled = True
575601 else :
576602 self .simulating = False
603+ self .reset_sim_btn .disabled = False
577604 if self .restart_simu :
578605 self .restart_simu = False
579606 self ._simulate ()
@@ -585,6 +612,13 @@ def _simulate(self):
585612 self .data_model_holding_registers .start_stop_simulation (
586613 self .simulating )
587614
615+ def reset_simulation (self , * args ):
616+ if not self .simulating :
617+ self .data_model_coil .reset_block_values ()
618+ self .data_model_discrete_inputs .reset_block_values ()
619+ self .data_model_input_registers .reset_block_values ()
620+ self .data_model_holding_registers .reset_block_values ()
621+
588622 def _sync_modbus_block_values (self ):
589623 """
590624 track external changes in modbus block values and sync GUI
@@ -633,6 +667,90 @@ def _restore(self):
633667 self .slave_count .text ) = self ._slave_misc [self .active_server ]
634668 self .slave_list ._trigger_reset_populate ()
635669
670+ def save_state (self ):
671+ with open (SLAVES_FILE , 'w' ) as f :
672+ slave = [int (slave_no ) for slave_no in self .slave_list .adapter .data ]
673+ slaves_memory = []
674+ for slaves , mem in self .data_map .iteritems ():
675+ for name , value in mem .iteritems ():
676+ if len (value ['data' ]) != 0 :
677+ slaves_memory .append ((slaves , name , map (int , value ['data' ].values ())))
678+
679+ dump (dict (
680+ slaves_list = slave , active_server = self .active_server ,
681+ port = self .port .text , slaves_memory = slaves_memory
682+ ), f , indent = 4 )
683+
684+ def load_state (self ):
685+ if not bool (eval (self .config .get ("State" , "load state" ))) or \
686+ not os .path .isfile (SLAVES_FILE ):
687+ return
688+
689+ with open (SLAVES_FILE , 'r' ) as f :
690+ try :
691+ data = load (f )
692+ except ValueError as e :
693+ self .show_error (
694+ "LoadError: Failed to load previous simulation state : %s "
695+ % e .message
696+ )
697+ return
698+
699+ if 'active_server' not in data or 'port' not in data \
700+ or 'slaves_list' not in data or 'slaves_memory' not in data :
701+ self .show_error ("LoadError: Failed to load previous simulation state : JSON Key "
702+ "Missing" )
703+ return
704+
705+ slaves_list = data ['slaves_list' ]
706+ if not len (slaves_list ):
707+ return
708+
709+ if data ['active_server' ] == 'tcp' :
710+ self .tcp .active = True
711+ self .serial .active = False
712+ self .interface_settings .current = self .tcp
713+ else :
714+ self .tcp .active = False
715+ self .serial .active = True
716+ self .interface_settings .current = self .serial
717+
718+ self .active_server = data ['active_server' ]
719+ self .port .text = data ['port' ]
720+
721+ self ._create_modbus_device ()
722+
723+ start_slave = 0
724+ temp_list = []
725+ slave_count = 1
726+ for first , second in zip (slaves_list [:- 1 ], slaves_list [1 :]):
727+ if first + 1 == second :
728+ slave_count += 1
729+ else :
730+ temp_list .append ((slaves_list [start_slave ], slave_count ))
731+ start_slave += slave_count
732+ slave_count = 1
733+ temp_list .append ((slaves_list [start_slave ], slave_count ))
734+
735+ for start_slave , slave_count in temp_list :
736+ self ._add_slaves (
737+ self .slave_list .adapter .selection ,
738+ self .slave_list .adapter .data ,
739+ (True , start_slave , slave_count )
740+ )
741+
742+ memory_map = {
743+ 'coils' : self .data_models .tab_list [3 ],
744+ 'discrete_inputs' : self .data_models .tab_list [2 ],
745+ 'input_registers' : self .data_models .tab_list [1 ],
746+ 'holding_registers' : self .data_models .tab_list [0 ]
747+ }
748+ slaves_memory = data ['slaves_memory' ]
749+ for slave_memory in slaves_memory :
750+ active_slave , memory_type , memory_data = slave_memory
751+ self ._update_data_models (active_slave , memory_map [memory_type ], len (memory_data ), memory_data )
752+
753+
636754setting_panel = """
637755[
638756 {
@@ -821,12 +939,22 @@ def _restore(self):
821939 {
822940 "type": "numeric",
823941 "title": "Time interval",
824- "desc": "When simulation is enabled, data is changed for eveery 'n' seconds defined here",
942+ "desc": "When simulation is enabled, data is changed for every 'n' seconds defined here",
825943 "section": "Simulation",
826944 "key": "time interval"
945+ },
946+ {
947+ "type": "title",
948+ "title": "State"
949+ },
950+ {
951+ "type": "bool",
952+ "title": "Load State",
953+ "desc": "Whether the previous state should be loaded or not, if not the original state is loaded",
954+ "section": "State",
955+ "key": "load state"
827956 }
828957
829-
830958]
831959"""
832960
@@ -844,6 +972,7 @@ def build(self):
844972 self .gui = Gui (
845973 modbus_log = os .path .join (self .user_data_dir , 'modbus.log' )
846974 )
975+ self .gui .load_state ()
847976 return self .gui
848977
849978 def on_pause (self ):
@@ -856,6 +985,8 @@ def on_stop(self):
856985 self .gui ._simulate ()
857986 self .gui .modbus_device .stop ()
858987 self .gui .sync_modbus_thread .cancel ()
988+ self .config .write ()
989+ self .gui .save_state ()
859990
860991 def show_settings (self , btn ):
861992 self .open_settings ()
@@ -894,6 +1025,9 @@ def build_config(self, config):
8941025 config .add_section ('Simulation' )
8951026 config .set ('Simulation' , 'time interval' , 1 )
8961027
1028+ config .add_section ('State' )
1029+ config .set ('State' , 'load state' , 1 )
1030+
8971031 def build_settings (self , settings ):
8981032 settings .register_type ("numeric_range" , SettingIntegerWithRange )
8991033 settings .add_json_panel ('Modbus Settings' , self .config ,
0 commit comments