def init(self, simulate=False, time_interval=1, **kwargs): """ Initializes Datamodel """ self.minval = kwargs.get("minval", self.minval) self.maxval = kwargs.get("maxval", self.maxval) self.blockname = kwargs.get("blockname", self.blockname) self.clear_widgets() self.simulate = simulate self.time_interval = time_interval dict_adapter = DictAdapter(data={}, args_converter=self.arg_converter, selection_mode='single', allow_empty_selection=True, cls=CompositeListItem ) # Use the adapter in our ListView: self.list_view = ListView(adapter=dict_adapter) self.add_widget(self.list_view) self.dispatcher = UpdateEventDispatcher() self._parent = kwargs.get('_parent', None) self.simulate_timer = BackgroundJob( "simulation", self.time_interval, self._simulate_block_values )
def __init__(self, cfg, **kwargs): super(Gui, self).__init__(**kwargs) time_interval = kwargs.get("time_interval", 1) self.slave_list.adapter.bind(on_selection_change=self.select_slave) self.data_model_loc.disabled = True self.slave_pane.disabled = True minval = kwargs.get("bin_min_val", 0) maxval = kwargs.get("bin_max_val", 1) self.data_model_coil.init( blockname="coils", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_discrete_inputs.init( blockname="discrete_inputs", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) minval = kwargs.get("reg_min_val", 0) maxval = kwargs.get("reg_max_val", 65535) self.block_start = kwargs.get("block_start", 0) self.block_size = kwargs.get("block_size", 100) self.data_model_input_registers.init( blockname="input_registers", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_holding_registers.init( blockname="holding_registers", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_loc.disabled = True configure_modbus_logger(cfg) self.modbus_device = ModBusSimu(port=int(self.port.text)) self.simu_time_interval = time_interval self.sync_modbus_thread = BackgroundJob( "modbus_sync", self.sync_modbus_time_interval, self._sync_modbus_block_values ) self.sync_modbus_thread.start()
def reinit(self, **kwargs): """ Re-initializes Datamodel on change in model configuration from settings :param kwargs: :return: """ self.minval = kwargs.get("minval", self.minval) self.maxval = kwargs.get("maxval", self.maxval) time_interval = kwargs.get("time_interval", None) try: if time_interval and int(time_interval) != self.time_interval: self.time_interval = time_interval if self.is_simulating: self.simulate_timer.cancel() self.simulate_timer = BackgroundJob("simulation", self.time_interval, self._simulate_block_values) self.dirty_thread = False self.start_stop_simulation(self.simulate) except ValueError: Logger.debug("Error while reinitializing DataModel %s" % kwargs)
def start_stop_simulation(self, simulate): """ Starts or stops simulating data :param simulate: :return: """ self.simulate = simulate if self.simulate: if self.dirty_thread: self.simulate_timer = BackgroundJob( "simulation", self.time_interval, self._simulate_block_values ) self.simulate_timer.start() self.dirty_thread = False self.is_simulating = True else: self.simulate_timer.cancel() self.dirty_thread = True self.is_simulating = False
class Gui(BoxLayout): ''' Gui of widgets. This is the root widget of the app. ''' # ---------------------GUI------------------------ # # Checkbox to select between tcp/serial interfaces = ObjectProperty() # Boxlayout to hold interface settings interface_settings = ObjectProperty() # TCP port port = ObjectProperty() # Toggle button to start/stop modbus server start_stop_server = ObjectProperty() # Container for slave list slave_pane = ObjectProperty() # slave start address textbox slave_start_add = ObjectProperty() # slave end address textbox slave_end_add = ObjectProperty() # Slave device count text box slave_count = ObjectProperty() # Slave list slave_list = ObjectProperty() # Container for modbus data models data_model_loc = ObjectProperty() # Tabbed panel to hold various modbus datamodels data_models = ObjectProperty() # Data models data_model_coil = ObjectProperty() data_model_discrete_inputs = ObjectProperty() data_model_input_registers = ObjectProperty() data_model_holding_registers = ObjectProperty() # Helpers slaves = ["%s" %i for i in xrange(1, 248)] data_map = {} active_slave = None server_running = False simulating = False simu_time_interval = None anim = None restart_simu = False sync_modbus_thread = None sync_modbus_time_interval = 5 def __init__(self, cfg, **kwargs): super(Gui, self).__init__(**kwargs) time_interval = kwargs.get("time_interval", 1) self.slave_list.adapter.bind(on_selection_change=self.select_slave) self.data_model_loc.disabled = True self.slave_pane.disabled = True minval = kwargs.get("bin_min_val", 0) maxval = kwargs.get("bin_max_val", 1) self.data_model_coil.init( blockname="coils", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_discrete_inputs.init( blockname="discrete_inputs", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) minval = kwargs.get("reg_min_val", 0) maxval = kwargs.get("reg_max_val", 65535) self.block_start = kwargs.get("block_start", 0) self.block_size = kwargs.get("block_size", 100) self.data_model_input_registers.init( blockname="input_registers", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_holding_registers.init( blockname="holding_registers", simulate=self.simulating, time_interval=time_interval, minval=minval, maxval=maxval, _parent=self ) self.data_model_loc.disabled = True configure_modbus_logger(cfg) self.modbus_device = ModBusSimu(port=int(self.port.text)) self.simu_time_interval = time_interval self.sync_modbus_thread = BackgroundJob( "modbus_sync", self.sync_modbus_time_interval, self._sync_modbus_block_values ) self.sync_modbus_thread.start() def start_server(self, btn): if btn.state == "down": self.modbus_device.start() self.server_running = True self.interface_settings.disabled = True self.interfaces.disabled = True self.slave_pane.disabled = False if len(self.slave_list.adapter.selection): self.data_model_loc.disabled = False if self.simulating: self._simulate() btn.text = "Stop" else: self.simulating = False self._simulate() self.modbus_device.stop() self.server_running = False self.interface_settings.disabled = False self.interfaces.disabled = False self.slave_pane.disabled = True self.data_model_loc.disabled = True btn.text = "Start" def update_tcp_connection_info(self, checkbox, value): if value: self.interface_settings.current = checkbox tcp_label = Label(text="Port") tcp_input = TextInput(text="5440", multiline=False) self.interface_settings.add_widget(tcp_label) self.interface_settings.add_widget(tcp_input) else: self.interface_settings.clear_widgets() def update_serial_connection_info(self, checkbox, value): if value: self.interface_settings.current = checkbox serial_label = Label(text="Serial Settings not supported !!") self.interface_settings.add_widget(serial_label) else: self.interface_settings.clear_widgets() def show_error(self, e): self.info_label.text = str(e) self.anim = Animation(top=190.0, opacity=1, d=2, t='in_back') +\ Animation(top=190.0, d=3) +\ Animation(top=0, opacity=0, d=2) self.anim.start(self.info_label) def add_slaves(self, *args): selected = self.slave_list.adapter.selection data = self.slave_list.adapter.data ret = self._process_slave_data(data) if ret[0]: start_slave_add, slave_count = ret[1:] else: return for slave_to_add in xrange(start_slave_add, start_slave_add + slave_count): if str(slave_to_add) in self.data_map: return self.data_map[str(slave_to_add)] = { "coils": { 'data': {}, 'item_strings': [], "instance": self.data_model_coil, "dirty": False }, "discrete_inputs": { 'data': {}, 'item_strings': [], "instance": self.data_model_discrete_inputs, "dirty": False }, "input_registers": { 'data': {}, 'item_strings': [], "instance": self.data_model_input_registers, "dirty": False }, "holding_registers": { 'data': {}, 'item_strings': [], "instance": self.data_model_holding_registers, "dirty": False } } self.modbus_device.add_slave(slave_to_add) for block_name, block_type in BLOCK_TYPES.items(): self.modbus_device.add_block(slave_to_add, block_name, block_type, self.block_start, self.block_size) data.append(str(slave_to_add)) self.slave_list.adapter.data = data self.slave_list._trigger_reset_populate() for item in selected: index = self.slave_list.adapter.data.index(item.text) if not self.slave_list.adapter.get_view(index).is_selected: self.slave_list.adapter.get_view(index).trigger_action( duration=0 ) self.slave_start_add.text = str(start_slave_add + slave_count) self.slave_end_add.text = self.slave_start_add.text self.slave_count.text = "1" def _process_slave_data(self, data): success = True data = sorted(data, key=int) # last_slave = 1 if not len(data) else data[-1] starting_address = int(self.slave_start_add.text) end_address = int(self.slave_end_add.text) if end_address < starting_address: end_address = starting_address try: slave_count = int(self.slave_count.text) except ValueError: slave_count = 1 if str(starting_address) in data: self.show_error("slave already present (%s)" % starting_address) success = False return [success] if starting_address < 1: self.show_error("slave address (%s)" " should be greater than 0 "% starting_address) success = False return [success] if starting_address > 247: self.show_error("slave address (%s)" " beyond supported modbus slave " "device address (247)" % starting_address) success = False return [success] size = (end_address - starting_address) + 1 size = slave_count if slave_count > size else size if (size + starting_address) > 247: self.show_error("address range (%s) beyond " "allowed modbus slave " "devices(247)" % (size + starting_address)) success = False return [success] self.slave_end_add.text = str(starting_address + size - 1) self.slave_count.text = str(size) return success, starting_address, size def delete_slaves(self, *args): selected = self.slave_list.adapter.selection slave = self.active_slave ct = self.data_models.current_tab for item in selected: self.modbus_device.remove_slave(int(item.text)) self.slave_list.adapter.data.remove(item.text) self.slave_list._trigger_reset_populate() ct.content.clear_widgets(make_dirty=True) if self.simulating: self.simulating = False self.restart_simu = True self._simulate() self.data_map.pop(slave) def update_data_models(self, *args): ct = self.data_models.current_tab current_tab = MAP[ct.text] ct.content.update_view() # self.data_map[self.active_slave][current_tab]['dirty'] = False _data = self.data_map[self.active_slave][current_tab] item_strings = _data['item_strings'] if len(item_strings) < self.block_size: updated_data, item_strings = ct.content.add_data(1, item_strings) _data['data'].update(updated_data) _data['item_strings'] = item_strings for k, v in updated_data.iteritems(): self.modbus_device.set_values(int(self.active_slave), current_tab, k, v) else: msg = ("OutOfModbusBlockError: address %s" " is out of block size %s" %(len(item_strings), self.block_size)) self.show_error(msg) def sync_data_callback(self, blockname, data): ct = self.data_models.current_tab current_tab = MAP[ct.text] if blockname != current_tab: current_tab = blockname try: _data = self.data_map[self.active_slave][current_tab] _data['data'].update(data) for k, v in data.iteritems(): self.modbus_device.set_values(int(self.active_slave), current_tab, k, int(v)) except KeyError: pass def delete_data_entry(self, *args): ct = self.data_models.current_tab current_tab = MAP[ct.text] _data = self.data_map[self.active_slave][current_tab] item_strings = _data['item_strings'] deleted, data = ct.content.delete_data(item_strings) dm = _data['data'] for index in deleted: dm.pop(index, None) if deleted: self.update_backend(int(self.active_slave), current_tab, data) msg = ("modbus-tk do not support deleting " "individual modbus register/discrete_inputs/coils" "The data is removed from GUI and the corresponding value is" "updated to '0' in backend . ") self.show_error(msg) def select_slave(self, adapter): ct = self.data_models.current_tab if len(adapter.selection) != 1: # Multiple selection - No Data Update ct.content.clear_widgets(make_dirty=True) if self.simulating: self.simulating = False self.restart_simu = True self._simulate() self.data_model_loc.disabled = True self.active_slave = None else: self.data_model_loc.disabled = False if self.restart_simu: self.simulating = True self.restart_simu = False self._simulate() self.active_slave = self.slave_list.adapter.selection[0].text self.refresh() def refresh(self): for child in self.data_models.tab_list: dm = self.data_map[self.active_slave][MAP[child.text]]['data'] child.content.refresh(dm) def update_backend(self, slave_id, blockname, new_data, ): self.modbus_device.remove_block(slave_id, blockname) self.modbus_device.add_block(slave_id, blockname, BLOCK_TYPES[blockname], 0, 100) for k, v in new_data.iteritems(): self.modbus_device.set_values(slave_id, blockname, k, int(v)) def change_simulation_settings(self, **kwargs): self.data_model_coil.reinit(**kwargs) self.data_model_discrete_inputs.reinit(**kwargs) self.data_model_input_registers.reinit(**kwargs) self.data_model_holding_registers.reinit(**kwargs) def change_datamodel_settings(self, key, value): if "max" in key: data = {"maxval": int(value)} else: data = {"minval": int(value)} if "bin" in key: self.data_model_coil.reinit(**data) self.data_model_discrete_inputs.reinit(**data) else: self.data_model_input_registers.reinit(**data) self.data_model_holding_registers.reinit(**data) def start_stop_simulation(self, btn): if btn.state == "down": self.simulating = True else: self.simulating = False if self.restart_simu: self.restart_simu = False self._simulate() def _simulate(self): self.data_model_coil.start_stop_simulation(self.simulating) self.data_model_discrete_inputs.start_stop_simulation(self.simulating) self.data_model_input_registers.start_stop_simulation(self.simulating) self.data_model_holding_registers.start_stop_simulation( self.simulating) def _sync_modbus_block_values(self): """ track external changes in modbus block values and sync GUI ToDo: A better way to update GUI when simulation is on going !! """ if not self.simulating: if self.active_slave: _data_map = self.data_map[self.active_slave] for block_name, value in _data_map.items(): updated = {} for k, v in value['data'].items(): actual_data = self.modbus_device.get_values( int(self.active_slave), block_name, int(k), 1 ) if actual_data[0] != int(v): updated[k] = actual_data[0] if updated: value['data'].update(updated) self.refresh()
class DataModel(GridLayout): """ Uses :class:`CompositeListItem` for list item views comprised by two :class:`ListItemButton`s and one :class:`ListItemLabel`. Illustrates how to construct the fairly involved args_converter used with :class:`CompositeListItem`. """ minval = NumericProperty(0) maxval = NumericProperty(0) simulate = False time_interval = 1 dirty_thread = False dirty_model = False simulate_timer = None simulate = False dispatcher = None list_view = None _parent = None is_simulating = False blockname = "<BLOCK_NAME_NOT_SET>" def __init__(self, **kwargs): kwargs['cols'] = 2 kwargs['size_hint'] = (1.0, 1.0) super(DataModel, self).__init__(**kwargs) self.init() def init(self, simulate=False, time_interval=1, **kwargs): """ Initializes Datamodel """ self.minval = kwargs.get("minval", self.minval) self.maxval = kwargs.get("maxval", self.maxval) self.blockname = kwargs.get("blockname", self.blockname) self.clear_widgets() self.simulate = simulate self.time_interval = time_interval dict_adapter = DictAdapter(data={}, args_converter=self.arg_converter, selection_mode='single', allow_empty_selection=True, cls=CompositeListItem ) # Use the adapter in our ListView: self.list_view = ListView(adapter=dict_adapter) self.add_widget(self.list_view) self.dispatcher = UpdateEventDispatcher() self._parent = kwargs.get('_parent', None) self.simulate_timer = BackgroundJob( "simulation", self.time_interval, self._simulate_block_values ) def clear_widgets(self, make_dirty=False, **kwargs): """ Overidden Clear widget function used while deselecting/deleting slave :param make_dirty: :param kwargs: :return: """ if make_dirty: self.dirty_model = True super(DataModel, self).clear_widgets(**kwargs) def reinit(self, **kwargs): """ Re-initializes Datamodel on change in model configuration from settings :param kwargs: :return: """ self.minval = kwargs.get("minval", self.minval) self.maxval = kwargs.get("maxval", self.maxval) time_interval = kwargs.get("time_interval", None) try: if time_interval and int(time_interval) != self.time_interval: self.time_interval = time_interval if self.is_simulating: self.simulate_timer.cancel() self.simulate_timer = BackgroundJob("simulation", self.time_interval, self._simulate_block_values) self.dirty_thread = False self.start_stop_simulation(self.simulate) except ValueError: Logger.debug("Error while reinitializing DataModel %s" % kwargs) def update_view(self): """ Updates view with listview again :return: """ if self.dirty_model: self.add_widget(self.list_view) self.dirty_model = False def arg_converter(self, index, data): """ arg converter to convert data to list view :param index: :param data: :return: """ _id = self.list_view.adapter.sorted_keys[index] return { 'text': str(_id), 'size_hint_y': None, 'height': 30, 'cls_dicts': [ { 'cls': ListItemButton, 'kwargs': {'text': str(_id)} }, { 'cls': NumericTextInput, 'kwargs': { 'data_model': self, 'minval': self.minval, 'maxval': self.maxval, 'text': str(data), 'multiline': False, 'is_representing_cls': True, } }, ] } def add_data(self, data, item_strings): """ Adds data to the Data model :param data: :param item_strings: :return: """ self.update_view() last_index = len(item_strings) if last_index in item_strings: last_index = int(item_strings[-1]) + 1 item_strings.append(last_index) self.list_view.adapter.data.update({last_index: data}) self.list_view._trigger_reset_populate() return self.list_view.adapter.data, item_strings def delete_data(self, item_strings): """ Delete data from data model :param item_strings: :return: """ selections = self.list_view.adapter.selection items_popped = [] for item in selections: index_popped = item_strings.pop(item_strings.index(int(item.text))) data_popped = self.list_view.adapter.data.pop(int(item.text), None) self.list_view.adapter.update_for_new_data() self.list_view._trigger_reset_populate() items_popped.append(index_popped) return items_popped, self.list_view.adapter.data def on_selection_change(self, item): pass def on_data_update(self, index, data): """ Call back function to update data when data is changed in the list view :param index: :param data: :return: """ self.list_view.adapter.data.update({index: data}) self.list_view._trigger_reset_populate() self.dispatcher.dispatch('on_update', self._parent, self.blockname, self.list_view.adapter.data) def refresh(self, data={}): """ Data model refresh function to update when the view when slave is selected :param data: :return: """ self.update_view() self.list_view.adapter.data = data self.list_view.disabled = False self.list_view._trigger_reset_populate() def start_stop_simulation(self, simulate): """ Starts or stops simulating data :param simulate: :return: """ self.simulate = simulate if self.simulate: if self.dirty_thread: self.simulate_timer = BackgroundJob( "simulation", self.time_interval, self._simulate_block_values ) self.simulate_timer.start() self.dirty_thread = False self.is_simulating = True else: self.simulate_timer.cancel() self.dirty_thread = True self.is_simulating = False def _simulate_block_values(self): if self.simulate: data = self.list_view.adapter.data if data: for index, value in data.items(): data[index] = randint(self.minval, self.maxval) print self.minval, self.maxval, data[index] self.refresh(data) self.dispatcher.dispatch('on_update', self._parent, self.blockname, self.list_view.adapter.data)