1717from utils .backgroundJob import BackgroundJob
1818import re
1919import os
20+ from json import load , dump
2021from kivy .config import Config
2122from kivy .lang import Builder
2223import ui .datamodel
2829 'holding registers' : 'holding_registers'
2930}
3031
32+ SLAVES_FILE = 'slaves.json'
33+
3134Builder .load_file ("templates/modbussimu.kv" )
3235
3336
@@ -53,6 +56,9 @@ class Gui(BoxLayout):
5356 # Checkbox to select between tcp/serial
5457 interfaces = ObjectProperty ()
5558
59+ tcp = ObjectProperty ()
60+ serial = ObjectProperty ()
61+
5662 # Boxlayout to hold interface settings
5763 interface_settings = ObjectProperty ()
5864
@@ -85,6 +91,8 @@ class Gui(BoxLayout):
8591 data_model_input_registers = ObjectProperty ()
8692 data_model_holding_registers = ObjectProperty ()
8793
94+ reset_sim_btn = ObjectProperty ()
95+
8896 # Helpers
8997 # slaves = ["%s" %i for i in xrange(1, 248)]
9098 _data_map = {"tcp" : {}, "rtu" : {}}
@@ -282,35 +290,41 @@ def _create_modbus_device(self):
282290
283291 def start_server (self , btn ):
284292 if btn .state == "down" :
285- self ._create_modbus_device ()
286-
287- self .modbus_device .start ()
288- self .server_running = True
289- self .interface_settings .disabled = True
290- self .interfaces .disabled = True
291- self .slave_pane .disabled = False
292- if len (self .slave_list .adapter .selection ):
293- self .data_model_loc .disabled = False
294- if self .simulating :
295- self ._simulate ()
296-
293+ self ._start_server ()
297294 btn .text = "Stop"
298-
299295 else :
300- self .simulating = False
301- self ._simulate ()
302- self .modbus_device .stop ()
303- self .server_running = False
304- self .interface_settings .disabled = False
305- self .interfaces .disabled = False
306- self .slave_pane .disabled = True
307- self .data_model_loc .disabled = True
296+ self ._stop_server ()
308297 btn .text = "Start"
309298
299+ def _start_server (self ):
300+ self ._create_modbus_device ()
301+
302+ self .modbus_device .start ()
303+ self .server_running = True
304+ self .interface_settings .disabled = True
305+ self .interfaces .disabled = True
306+ self .slave_pane .disabled = False
307+ if len (self .slave_list .adapter .selection ):
308+ self .data_model_loc .disabled = False
309+ if self .simulating :
310+ self ._simulate ()
311+
312+ def _stop_server (self ):
313+ self .simulating = False
314+ self ._simulate ()
315+ self .modbus_device .stop ()
316+ self .server_running = False
317+ self .interface_settings .disabled = False
318+ self .interfaces .disabled = False
319+ self .slave_pane .disabled = True
320+ self .data_model_loc .disabled = True
321+
310322 def update_tcp_connection_info (self , checkbox , value ):
311323 self .active_server = "tcp"
312324 if value :
313325 self .interface_settings .current = checkbox
326+ if self .last_active_port ['tcp' ] == "" :
327+ self .last_active_port ['tcp' ] = 5440
314328 self .port .text = self .last_active_port ['tcp' ]
315329 self ._restore ()
316330 else :
@@ -325,7 +339,6 @@ def update_serial_connection_info(self, checkbox, value):
325339 self .last_active_port ['serial' ] = '/dev/ptyp0'
326340 self .port .text = self .last_active_port ['serial' ]
327341 self ._restore ()
328-
329342 else :
330343 self .last_active_port ['serial' ] = self .port .text
331344 self ._backup ()
@@ -341,10 +354,14 @@ def add_slaves(self, *args):
341354 selected = self .slave_list .adapter .selection
342355 data = self .slave_list .adapter .data
343356 ret = self ._process_slave_data (data )
357+ self ._add_slaves (selected , data , ret )
358+
359+ def _add_slaves (self , selected , data , ret ):
344360 if ret [0 ]:
345361 start_slave_add , slave_count = ret [1 :]
346362 else :
347363 return
364+
348365 for slave_to_add in xrange (start_slave_add ,
349366 start_slave_add + slave_count ):
350367 if str (slave_to_add ) in self .data_map :
@@ -378,7 +395,7 @@ def add_slaves(self, *args):
378395 self .modbus_device .add_slave (slave_to_add )
379396 for block_name , block_type in BLOCK_TYPES .items ():
380397 self .modbus_device .add_block (slave_to_add ,
381- block_name , block_type , self .block_start , self .block_size )
398+ block_name , block_type , self .block_start , self .block_size )
382399
383400 data .append (str (slave_to_add ))
384401 self .slave_list .adapter .data = data
@@ -452,25 +469,32 @@ def delete_slaves(self, *args):
452469 self .data_map .pop (slave )
453470
454471 def update_data_models (self , * args ):
455- ct = self .data_models .current_tab
472+ active = self .active_slave
473+ tab = self .data_models .current_tab
474+ count = self .data_count .text
475+ self ._update_data_models (active , tab , count , 1 )
476+
477+ def _update_data_models (self , active , tab , count , value ):
478+ ct = tab
456479 current_tab = MAP [ct .text ]
457480
458481 ct .content .update_view ()
459482 # self.data_map[self.active_slave][current_tab]['dirty'] = False
460- _data = self .data_map [self . active_slave ][current_tab ]
483+ _data = self .data_map [active ][current_tab ]
461484 item_strings = _data ['item_strings' ]
462- for i in xrange (int (self . data_count . text )):
485+ for i in xrange (int (count )):
463486 if len (item_strings ) < self .block_size :
464- updated_data , item_strings = ct .content .add_data (1 , item_strings )
487+ _value = 1 if isinstance (value , int ) else value [i ]
488+ updated_data , item_strings = ct .content .add_data (_value , item_strings )
465489 _data ['data' ].update (updated_data )
466490 _data ['item_strings' ] = item_strings
467491 for k , v in updated_data .iteritems ():
468- self .modbus_device .set_values (int (self . active_slave ),
492+ self .modbus_device .set_values (int (active ),
469493 current_tab , k , v )
470494 else :
471495 msg = ("OutOfModbusBlockError: address %s"
472- " is out of block size %s" % (len (item_strings ),
473- self .block_size ))
496+ " is out of block size %s" % (len (item_strings ),
497+ self .block_size ))
474498 self .show_error (msg )
475499 break
476500
@@ -562,8 +586,10 @@ def change_datamodel_settings(self, key, value):
562586 def start_stop_simulation (self , btn ):
563587 if btn .state == "down" :
564588 self .simulating = True
589+ self .reset_sim_btn .disabled = True
565590 else :
566591 self .simulating = False
592+ self .reset_sim_btn .disabled = False
567593 if self .restart_simu :
568594 self .restart_simu = False
569595 self ._simulate ()
@@ -575,6 +601,13 @@ def _simulate(self):
575601 self .data_model_holding_registers .start_stop_simulation (
576602 self .simulating )
577603
604+ def reset_simulation (self , * args ):
605+ if not self .simulating :
606+ self .data_model_coil .reset_block_values ()
607+ self .data_model_discrete_inputs .reset_block_values ()
608+ self .data_model_input_registers .reset_block_values ()
609+ self .data_model_holding_registers .reset_block_values ()
610+
578611 def _sync_modbus_block_values (self ):
579612 """
580613 track external changes in modbus block values and sync GUI
@@ -623,6 +656,90 @@ def _restore(self):
623656 self .slave_count .text ) = self ._slave_misc [self .active_server ]
624657 self .slave_list ._trigger_reset_populate ()
625658
659+ def save_state (self ):
660+ with open (SLAVES_FILE , 'w' ) as f :
661+ slave = [int (slave_no ) for slave_no in self .slave_list .adapter .data ]
662+ slaves_memory = []
663+ for slaves , mem in self .data_map .iteritems ():
664+ for name , value in mem .iteritems ():
665+ if len (value ['data' ]) != 0 :
666+ slaves_memory .append ((slaves , name , map (int , value ['data' ].values ())))
667+
668+ dump (dict (
669+ slaves_list = slave , active_server = self .active_server ,
670+ port = self .port .text , slaves_memory = slaves_memory
671+ ), f , indent = 4 )
672+
673+ def load_state (self ):
674+ if not bool (eval (self .config .get ("State" , "load state" ))) or \
675+ not os .path .isfile (SLAVES_FILE ):
676+ return
677+
678+ with open (SLAVES_FILE , 'r' ) as f :
679+ try :
680+ data = load (f )
681+ except ValueError as e :
682+ self .show_error (
683+ "LoadError: Failed to load previous simulation state : %s "
684+ % e .message
685+ )
686+ return
687+
688+ if 'active_server' not in data or 'port' not in data \
689+ or 'slaves_list' not in data or 'slaves_memory' not in data :
690+ self .show_error ("LoadError: Failed to load previous simulation state : JSON Key "
691+ "Missing" )
692+ return
693+
694+ slaves_list = data ['slaves_list' ]
695+ if not len (slaves_list ):
696+ return
697+
698+ if data ['active_server' ] == 'tcp' :
699+ self .tcp .active = True
700+ self .serial .active = False
701+ self .interface_settings .current = self .tcp
702+ else :
703+ self .tcp .active = False
704+ self .serial .active = True
705+ self .interface_settings .current = self .serial
706+
707+ self .active_server = data ['active_server' ]
708+ self .port .text = data ['port' ]
709+
710+ self ._create_modbus_device ()
711+
712+ start_slave = 0
713+ temp_list = []
714+ slave_count = 1
715+ for first , second in zip (slaves_list [:- 1 ], slaves_list [1 :]):
716+ if first + 1 == second :
717+ slave_count += 1
718+ else :
719+ temp_list .append ((slaves_list [start_slave ], slave_count ))
720+ start_slave += slave_count
721+ slave_count = 1
722+ temp_list .append ((slaves_list [start_slave ], slave_count ))
723+
724+ for start_slave , slave_count in temp_list :
725+ self ._add_slaves (
726+ self .slave_list .adapter .selection ,
727+ self .slave_list .adapter .data ,
728+ (True , start_slave , slave_count )
729+ )
730+
731+ memory_map = {
732+ 'coils' : self .data_models .tab_list [3 ],
733+ 'discrete_inputs' : self .data_models .tab_list [2 ],
734+ 'input_registers' : self .data_models .tab_list [1 ],
735+ 'holding_registers' : self .data_models .tab_list [0 ]
736+ }
737+ slaves_memory = data ['slaves_memory' ]
738+ for slave_memory in slaves_memory :
739+ active_slave , memory_type , memory_data = slave_memory
740+ self ._update_data_models (active_slave , memory_map [memory_type ], len (memory_data ), memory_data )
741+
742+
626743setting_panel = """
627744[
628745 {
@@ -811,12 +928,22 @@ def _restore(self):
811928 {
812929 "type": "numeric",
813930 "title": "Time interval",
814- "desc": "When simulation is enabled, data is changed for eveery 'n' seconds defined here",
931+ "desc": "When simulation is enabled, data is changed for every 'n' seconds defined here",
815932 "section": "Simulation",
816933 "key": "time interval"
934+ },
935+ {
936+ "type": "title",
937+ "title": "State"
938+ },
939+ {
940+ "type": "bool",
941+ "title": "Load State",
942+ "desc": "Whether the previous state should be loaded or not, if not the original state is loaded",
943+ "section": "State",
944+ "key": "load state"
817945 }
818946
819-
820947]
821948"""
822949
@@ -831,10 +958,10 @@ class ModbusSimuApp(App):
831958 settings_cls = SettingsWithSidebar
832959
833960 def build (self ):
834-
835961 self .gui = Gui (
836962 modbus_log = os .path .join (self .user_data_dir , 'modbus.log' )
837963 )
964+ self .gui .load_state ()
838965 return self .gui
839966
840967 def on_pause (self ):
@@ -847,6 +974,8 @@ def on_stop(self):
847974 self .gui ._simulate ()
848975 self .gui .modbus_device .stop ()
849976 self .gui .sync_modbus_thread .cancel ()
977+ self .config .write ()
978+ self .gui .save_state ()
850979
851980 def show_settings (self , btn ):
852981 self .open_settings ()
@@ -885,6 +1014,9 @@ def build_config(self, config):
8851014 config .add_section ('Simulation' )
8861015 config .set ('Simulation' , 'time interval' , 1 )
8871016
1017+ config .add_section ('State' )
1018+ config .set ('State' , 'load state' , 1 )
1019+
8881020 def build_settings (self , settings ):
8891021 settings .register_type ("numeric_range" , SettingIntegerWithRange )
8901022 settings .add_json_panel ('Modbus Settings' , self .config ,
0 commit comments