2
#---------------------------------------------------------------------------#
4
#---------------------------------------------------------------------------#
8
from threading import Thread
10
#---------------------------------------------------------------------------#
12
#---------------------------------------------------------------------------#
13
from twisted.internet import gtk2reactor
18
#---------------------------------------------------------------------------#
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
26
#--------------------------------------------------------------------------#
28
#--------------------------------------------------------------------------#
30
log = logging.getLogger(__name__)
32
#---------------------------------------------------------------------------#
34
#---------------------------------------------------------------------------#
35
class ConfigurationException(Exception):
36
''' Exception for configuration error '''
38
def __init__(self, string):
39
Exception.__init__(self, string)
43
return 'Configuration Error: %s' % self.string
45
#---------------------------------------------------------------------------#
46
# Extra Global Functions
47
#---------------------------------------------------------------------------#
48
# These are extra helper functions that don't belong in a class
49
#---------------------------------------------------------------------------#
51
''' Simple test to see if we are running as root '''
52
return getpass.getuser() == "root"
54
#---------------------------------------------------------------------------#
56
#---------------------------------------------------------------------------#
57
class Simulator(object):
59
Class used to parse configuration file and create and modbus
62
The format of the configuration file is actually just a
63
python pickle, which is a compressed memory dump from
67
def __init__(self, config):
69
Trys to load a configuration file, lets the file not
70
found exception fall through
72
@param config The pickled datastore
75
self.file = open(config, "r")
77
raise ConfigurationException("File not found %s" % config)
80
''' Parses the config file and creates a server context '''
82
handle = pickle.load(self.file)
88
raise ConfigurationException("Invalid Configuration")
89
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
90
return ModbusServerContext(slaves=slave)
93
''' Starts the snmp simulator '''
94
ports = [502]+range(20000,25000)
97
reactor.listenTCP(port, ModbusServerFactory(self._parse()))
98
print 'listening on port', port
100
except twisted_error.CannotListenError:
104
''' Used to run the simulator '''
105
reactor.callWhenRunning(self._simulator)
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):
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)
120
Thread.__init__(self)
124
''' Run the network reset '''
125
os.system("/etc/init.d/networking restart")
127
#---------------------------------------------------------------------------#
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
#---------------------------------------------------------------------------#
134
#===================================================================
135
#--- simulator.py (revision 60)
136
#+++ simulator.py (working copy)
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
145
# self.tree.signal_autoconnect(actions)
150
#+ self.file_changed(self.tdevice)
151
# if os.path.exists(self.file):
153
# handle = Simulator(config=self.file)
154
#---------------------------------------------------------------------------#
155
class SimulatorApp(object):
157
This class implements the GUI for the flasher application
164
def __init__(self, xml):
165
''' Sets up the gui, callback, and widget handles '''
167
#---------------------------------------------------------------------------#
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")
179
#---------------------------------------------------------------------------#
181
#---------------------------------------------------------------------------#
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
189
self.tree.signal_autoconnect(actions)
191
self.error_dialog("This program must be run with root permissions!", True)
193
#---------------------------------------------------------------------------#
195
#---------------------------------------------------------------------------#
196
# Not callbacks, but used by them
197
#---------------------------------------------------------------------------#
198
def show_buttons(self, state=False, all=0):
199
''' Greys out the buttons '''
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)
207
def destroy_interfaces(self):
208
''' This is used to reset the virtual interfaces '''
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')
223
dialog.connect("response", lambda w, r: gtk.main_quit())
225
dialog.connect("response", lambda w, r: w.destroy())
228
#---------------------------------------------------------------------------#
230
#---------------------------------------------------------------------------#
231
# These are all callbacks for the various buttons
232
#---------------------------------------------------------------------------#
233
def start_clicked(self, widget):
234
''' Starts the simulator '''
238
# check starting network
239
net = self.tsubnet.get_text()
240
octets = net.split('.')
242
base = "%s.%s" % (octets[0], octets[1])
243
net = int(octets[2]) % 255
244
start = int(octets[3]) % 255
246
self.error_dialog("Invalid starting address!");
249
# check interface size
250
size = int(self.tnumber.get_text())
252
for i in range(start, (size + start)):
254
cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j)
256
if j == 254: net = net + 1
259
self.error_dialog("Invalid number of devices!");
263
if os.path.exists(self.file):
264
self.show_buttons(state=False)
266
handle = Simulator(config=self.file)
268
except ConfigurationException, ex:
269
self.error_dialog("Error %s" % ex)
270
self.show_buttons(state=True)
272
self.error_dialog("Device to emulate does not exist!");
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())
288
def close_clicked(self, widget):
289
''' Callback for close button '''
290
self.destroy_interfaces()
291
reactor.stop() # quit twisted
293
def file_changed(self, widget):
294
''' Callback for the filename change '''
295
self.file = widget.get_filename()
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
#---------------------------------------------------------------------------#
305
Main control function
306
This either launches the gui or runs the command line application
311
log.setLevel(logging.DEBUG)
312
logging.basicConfig()
314
print "Logging is not supported on this system"
315
simulator = SimulatorApp('./simulator.glade')
318
#---------------------------------------------------------------------------#
319
# Library/Console Test
320
#---------------------------------------------------------------------------#
321
# If this is called from console, we start main
322
#---------------------------------------------------------------------------#
323
if __name__ == "__main__":