Skip to content

Commit cb786d2

Browse files
Krishna Tripathidhoomakethu
authored andcommitted
Merged in BEDGE-2699-modbus-simulation-improvement (pull request #1)
BEDGE-2699 modbus simulation improvement [READY] Approved-by: Sanjay K V <[email protected]>
2 parents 750ead1 + 3cdffd5 commit cb786d2

File tree

4 files changed

+187
-38
lines changed

4 files changed

+187
-38
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,7 @@ target/
6262
.idea
6363

6464
#IniFiles
65-
*.ini
65+
*.ini
66+
67+
#JSONFiles
68+
*.json

main.py

Lines changed: 165 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from utils.backgroundJob import BackgroundJob
1818
import re
1919
import os
20+
from json import load, dump
2021
from kivy.config import Config
2122
from kivy.lang import Builder
2223
import ui.datamodel
@@ -28,6 +29,8 @@
2829
'holding registers': 'holding_registers'
2930
}
3031

32+
SLAVES_FILE = 'slaves.json'
33+
3134
Builder.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+
626743
setting_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,

templates/modbussimu.kv

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
data_models: data_model_screen
2424
info_label: info_lbl
2525
interfaces: interfaces
26+
tcp: chkbx
27+
serial: chkbx2
2628
port: txtBox
2729
interface_settings: interface_settings
2830
orientation: 'vertical'
@@ -39,6 +41,7 @@
3941
slave_start_add: slave_start_add
4042
slave_end_add: slave_end_add
4143
action_bar: action_bar
44+
reset_sim_btn: reset_simulation
4245
BoxLayout:
4346
padding: '2sp'
4447
canvas:
@@ -70,7 +73,7 @@
7073
Label:
7174
text: "Serial"
7275
CheckBox:
73-
id: "chkbx2"
76+
id: chkbx2
7477
group: "interface"
7578
on_active: root.update_serial_connection_info(*args)
7679
Widget:
@@ -292,6 +295,10 @@
292295
app_icon: 'assets/riptideLogo.png'
293296
disabled: True
294297
ActionOverflow:
298+
ActionButton:
299+
id: reset_simulation
300+
text: 'Reset Simulation'
301+
on_release: root.reset_simulation(*args)
295302
ActionToggleButton:
296303
text: 'Simulate'
297304
on_release: root.start_stop_simulation(*args)

0 commit comments

Comments
 (0)