~ubuntu-branches/ubuntu/raring/pymodbus/raring-proposed

« back to all changes in this revision

Viewing changes to examples/gui/gtk/simulator.py

  • Committer: Package Import Robot
  • Author(s): W. Martin Borgert
  • Date: 2011-10-26 07:26:28 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20111026072628-fvzyi6tnb8iipomp
Tags: 0.9.0+r175-1
* Update from trunk to get a number of upstream fixes.
* Removed examples/tools/ (not present in previous version
  anyway) from source because there are different licenses
  involved. Needs clarification.
* Dont't install unit tests.
* Debian patches not necessary anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#---------------------------------------------------------------------------#
 
3
# System
 
4
#---------------------------------------------------------------------------#
 
5
import os
 
6
import getpass
 
7
import pickle
 
8
from threading import Thread
 
9
 
 
10
#---------------------------------------------------------------------------#
 
11
# For Gui
 
12
#---------------------------------------------------------------------------#
 
13
from twisted.internet import gtk2reactor
 
14
gtk2reactor.install()
 
15
import gtk
 
16
from gtk import glade
 
17
 
 
18
#---------------------------------------------------------------------------#
 
19
# SNMP Simulator
 
20
#---------------------------------------------------------------------------#
 
21
from twisted.internet import reactor
 
22
from twisted.internet import error as twisted_error
 
23
from pymodbus.server.async import ModbusServerFactory
 
24
from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext
 
25
 
 
26
#--------------------------------------------------------------------------#
 
27
# Logging
 
28
#--------------------------------------------------------------------------#
 
29
import logging
 
30
log = logging.getLogger(__name__)
 
31
 
 
32
#---------------------------------------------------------------------------#
 
33
# Application Error
 
34
#---------------------------------------------------------------------------#
 
35
class ConfigurationException(Exception):
 
36
    ''' Exception for configuration error '''
 
37
 
 
38
    def __init__(self, string):
 
39
        Exception.__init__(self, string)
 
40
        self.string = string
 
41
 
 
42
    def __str__(self):
 
43
        return 'Configuration Error: %s' % self.string
 
44
 
 
45
#---------------------------------------------------------------------------#
 
46
# Extra Global Functions
 
47
#---------------------------------------------------------------------------#
 
48
# These are extra helper functions that don't belong in a class
 
49
#---------------------------------------------------------------------------#
 
50
def root_test():
 
51
    ''' Simple test to see if we are running as root '''
 
52
    return getpass.getuser() == "root"
 
53
 
 
54
#---------------------------------------------------------------------------#
 
55
# Simulator Class
 
56
#---------------------------------------------------------------------------#
 
57
class Simulator(object):
 
58
    '''
 
59
    Class used to parse configuration file and create and modbus
 
60
    datastore.
 
61
 
 
62
    The format of the configuration file is actually just a
 
63
    python pickle, which is a compressed memory dump from
 
64
    the scraper.
 
65
    '''
 
66
 
 
67
    def __init__(self, config):
 
68
        '''
 
69
        Trys to load a configuration file, lets the file not
 
70
        found exception fall through
 
71
 
 
72
        @param config The pickled datastore
 
73
        '''
 
74
        try:
 
75
            self.file = open(config, "r")
 
76
        except Exception:
 
77
            raise ConfigurationException("File not found %s" % config)
 
78
 
 
79
    def _parse(self):
 
80
        ''' Parses the config file and creates a server context '''
 
81
        try:
 
82
            handle = pickle.load(self.file)
 
83
            dsd = handle['di']
 
84
            csd = handle['ci']
 
85
            hsd = handle['hr']
 
86
            isd = handle['ir']
 
87
        except KeyError:
 
88
            raise ConfigurationException("Invalid Configuration")
 
89
        slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
 
90
        return ModbusServerContext(slaves=slave)
 
91
 
 
92
    def _simulator(self):
 
93
        ''' Starts the snmp simulator '''
 
94
        ports = [502]+range(20000,25000)
 
95
        for port in ports:
 
96
            try:
 
97
                reactor.listenTCP(port, ModbusServerFactory(self._parse()))
 
98
                print 'listening on port', port
 
99
                return port
 
100
            except twisted_error.CannotListenError:
 
101
                pass
 
102
 
 
103
    def run(self):
 
104
        ''' Used to run the simulator '''
 
105
        reactor.callWhenRunning(self._simulator)
 
106
 
 
107
#---------------------------------------------------------------------------#
 
108
# Network reset thread
 
109
#---------------------------------------------------------------------------#
 
110
# This is linux only, maybe I should make a base class that can be filled
 
111
# in for linux(debian/redhat)/windows/nix
 
112
#---------------------------------------------------------------------------#
 
113
class NetworkReset(Thread):
 
114
    '''
 
115
    This class is simply a daemon that is spun off at the end of the
 
116
    program to call the network restart function (an easy way to
 
117
    remove all the virtual interfaces)
 
118
    '''
 
119
    def __init__(self):
 
120
        Thread.__init__(self)
 
121
        self.setDaemon(True)
 
122
 
 
123
    def run(self):
 
124
        ''' Run the network reset '''
 
125
        os.system("/etc/init.d/networking restart")
 
126
 
 
127
#---------------------------------------------------------------------------#
 
128
# Main Gui Class
 
129
#---------------------------------------------------------------------------#
 
130
# Note, if you are using gtk2 before 2.12, the file_set signal is not
 
131
# introduced.  To fix this, you need to apply the following patch
 
132
#---------------------------------------------------------------------------#
 
133
#Index: simulator.py
 
134
#===================================================================
 
135
#--- simulator.py       (revision 60)
 
136
#+++ simulator.py       (working copy)
 
137
#@@ -158,7 +161,7 @@
 
138
#                       "on_helpBtn_clicked"    : self.help_clicked,
 
139
#                       "on_quitBtn_clicked"    : self.close_clicked,
 
140
#                       "on_startBtn_clicked"   : self.start_clicked,
 
141
#-                      "on_file_changed"       : self.file_changed,
 
142
#+                      #"on_file_changed"      : self.file_changed,
 
143
#                       "on_window_destroy"     : self.close_clicked
 
144
#               }
 
145
#               self.tree.signal_autoconnect(actions)
 
146
#@@ -235,6 +238,7 @@
 
147
#                       return False
 
148
#
 
149
#               # check input file
 
150
#+              self.file_changed(self.tdevice)
 
151
#               if os.path.exists(self.file):
 
152
#                       self.grey_out()
 
153
#                       handle = Simulator(config=self.file)
 
154
#---------------------------------------------------------------------------#
 
155
class SimulatorApp(object):
 
156
    '''
 
157
    This class implements the GUI for the flasher application
 
158
    '''
 
159
    file = "none"
 
160
    subnet = 205
 
161
    number = 1
 
162
    restart = 0
 
163
 
 
164
    def __init__(self, xml):
 
165
        ''' Sets up the gui, callback, and widget handles '''
 
166
 
 
167
        #---------------------------------------------------------------------------#
 
168
        # Action Handles
 
169
        #---------------------------------------------------------------------------#
 
170
        self.tree    = glade.XML(xml)
 
171
        self.bstart  = self.tree.get_widget("startBtn")
 
172
        self.bhelp   = self.tree.get_widget("helpBtn")
 
173
        self.bclose  = self.tree.get_widget("quitBtn")
 
174
        self.window  = self.tree.get_widget("window")
 
175
        self.tdevice = self.tree.get_widget("fileTxt")
 
176
        self.tsubnet = self.tree.get_widget("addressTxt")
 
177
        self.tnumber = self.tree.get_widget("deviceTxt")
 
178
 
 
179
        #---------------------------------------------------------------------------#
 
180
        # Actions
 
181
        #---------------------------------------------------------------------------#
 
182
        actions = {
 
183
            "on_helpBtn_clicked"  : self.help_clicked,
 
184
            "on_quitBtn_clicked"  : self.close_clicked,
 
185
            "on_startBtn_clicked" : self.start_clicked,
 
186
            "on_file_changed"     : self.file_changed,
 
187
            "on_window_destroy"   : self.close_clicked
 
188
        }
 
189
        self.tree.signal_autoconnect(actions)
 
190
        if not root_test():
 
191
            self.error_dialog("This program must be run with root permissions!", True)
 
192
 
 
193
#---------------------------------------------------------------------------#
 
194
# Gui helpers
 
195
#---------------------------------------------------------------------------#
 
196
# Not callbacks, but used by them
 
197
#---------------------------------------------------------------------------#
 
198
    def show_buttons(self, state=False, all=0):
 
199
        ''' Greys out the buttons '''
 
200
        if all:
 
201
            self.window.set_sensitive(state)
 
202
        self.bstart.set_sensitive(state)
 
203
        self.tdevice.set_sensitive(state)
 
204
        self.tsubnet.set_sensitive(state)
 
205
        self.tnumber.set_sensitive(state)
 
206
 
 
207
    def destroy_interfaces(self):
 
208
        ''' This is used to reset the virtual interfaces '''
 
209
        if self.restart:
 
210
            n = NetworkReset()
 
211
            n.start()
 
212
 
 
213
    def error_dialog(self, message, quit=False):
 
214
        ''' Quick pop-up for error messages '''
 
215
        dialog = gtk.MessageDialog(
 
216
            parent         = self.window,
 
217
            flags          = gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
 
218
            type           = gtk.MESSAGE_ERROR,
 
219
            buttons        = gtk.BUTTONS_CLOSE,
 
220
            message_format = message)
 
221
        dialog.set_title('Error')
 
222
        if quit:
 
223
            dialog.connect("response", lambda w, r: gtk.main_quit())
 
224
        else:
 
225
            dialog.connect("response", lambda w, r: w.destroy())
 
226
        dialog.show()
 
227
 
 
228
#---------------------------------------------------------------------------#
 
229
# Button Actions
 
230
#---------------------------------------------------------------------------#
 
231
# These are all callbacks for the various buttons
 
232
#---------------------------------------------------------------------------#
 
233
    def start_clicked(self, widget):
 
234
        ''' Starts the simulator '''
 
235
        start = 1
 
236
        base = "172.16"
 
237
 
 
238
        # check starting network
 
239
        net = self.tsubnet.get_text()
 
240
        octets = net.split('.')
 
241
        if len(octets) == 4:
 
242
            base = "%s.%s" % (octets[0], octets[1])
 
243
            net = int(octets[2]) % 255
 
244
            start = int(octets[3]) % 255
 
245
        else:
 
246
            self.error_dialog("Invalid starting address!");
 
247
            return False
 
248
 
 
249
        # check interface size
 
250
        size = int(self.tnumber.get_text())
 
251
        if (size >= 1):
 
252
            for i in range(start, (size + start)):
 
253
                j = i % 255
 
254
                cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j)
 
255
                os.system(cmd)
 
256
                if j == 254: net = net + 1
 
257
            self.restart = 1
 
258
        else:
 
259
            self.error_dialog("Invalid number of devices!");
 
260
            return False
 
261
 
 
262
        # check input file
 
263
        if os.path.exists(self.file):
 
264
            self.show_buttons(state=False)
 
265
            try:
 
266
                handle = Simulator(config=self.file)
 
267
                handle.run()
 
268
            except ConfigurationException, ex:
 
269
                self.error_dialog("Error %s" % ex)
 
270
                self.show_buttons(state=True)
 
271
        else:
 
272
            self.error_dialog("Device to emulate does not exist!");
 
273
            return False
 
274
 
 
275
    def help_clicked(self, widget):
 
276
        ''' Quick pop-up for about page '''
 
277
        data = gtk.AboutDialog()
 
278
        data.set_version("0.1")
 
279
        data.set_name(('Modbus Simulator'))
 
280
        data.set_authors(["Galen Collins"])
 
281
        data.set_comments(('First Select a device to simulate,\n'
 
282
            + 'then select the starting subnet of the new devices\n'
 
283
            + 'then select the number of device to simulate and click start'))
 
284
        data.set_website("http://code.google.com/p/pymodbus/")
 
285
        data.connect("response", lambda w,r: w.hide())
 
286
        data.run()
 
287
 
 
288
    def close_clicked(self, widget):
 
289
        ''' Callback for close button '''
 
290
        self.destroy_interfaces()
 
291
        reactor.stop()          # quit twisted
 
292
 
 
293
    def file_changed(self, widget):
 
294
        ''' Callback for the filename change '''
 
295
        self.file = widget.get_filename()
 
296
 
 
297
#---------------------------------------------------------------------------#
 
298
# Main handle function
 
299
#---------------------------------------------------------------------------#
 
300
# This is called when the application is run from a console
 
301
# We simply start the gui and start the twisted event loop
 
302
#---------------------------------------------------------------------------#
 
303
def main():
 
304
    '''
 
305
    Main control function
 
306
    This either launches the gui or runs the command line application
 
307
    '''
 
308
    debug = True
 
309
    if debug:
 
310
        try:
 
311
            log.setLevel(logging.DEBUG)
 
312
            logging.basicConfig()
 
313
        except Exception, e:
 
314
            print "Logging is not supported on this system"
 
315
    simulator = SimulatorApp('./simulator.glade')
 
316
    reactor.run()
 
317
 
 
318
#---------------------------------------------------------------------------#
 
319
# Library/Console Test
 
320
#---------------------------------------------------------------------------#
 
321
# If this is called from console, we start main
 
322
#---------------------------------------------------------------------------#
 
323
if __name__ == "__main__":
 
324
    main()