3
Note that this is not finished
5
#---------------------------------------------------------------------------#
7
#---------------------------------------------------------------------------#
11
from threading import Thread
13
#---------------------------------------------------------------------------#
15
#---------------------------------------------------------------------------#
17
from twisted.internet import wxreactor
20
#---------------------------------------------------------------------------#
22
#---------------------------------------------------------------------------#
23
from twisted.internet import reactor
24
from twisted.internet import error as twisted_error
25
from pymodbus.server.async import ModbusServerFactory
26
from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext
28
#--------------------------------------------------------------------------#
30
#--------------------------------------------------------------------------#
32
log = logging.getLogger(__name__)
34
#---------------------------------------------------------------------------#
36
#---------------------------------------------------------------------------#
37
class ConfigurationException(Exception):
38
''' Exception for configuration error '''
41
#---------------------------------------------------------------------------#
42
# Extra Global Functions
43
#---------------------------------------------------------------------------#
44
# These are extra helper functions that don't belong in a class
45
#---------------------------------------------------------------------------#
47
''' Simple test to see if we are running as root '''
48
return getpass.getuser() == "root"
50
#---------------------------------------------------------------------------#
52
#---------------------------------------------------------------------------#
53
class Simulator(object):
55
Class used to parse configuration file and create and modbus
58
The format of the configuration file is actually just a
59
python pickle, which is a compressed memory dump from
63
def __init__(self, config):
65
Trys to load a configuration file, lets the file not
66
found exception fall through
68
@param config The pickled datastore
71
self.file = open(config, "r")
73
raise ConfigurationException("File not found %s" % config)
76
''' Parses the config file and creates a server context '''
78
handle = pickle.load(self.file)
84
raise ConfigurationException("Invalid Configuration")
85
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
86
return ModbusServerContext(slaves=slave)
89
''' Starts the snmp simulator '''
90
ports = [502]+range(20000,25000)
93
reactor.listenTCP(port, ModbusServerFactory(self._parse()))
94
print 'listening on port', port
96
except twisted_error.CannotListenError:
100
''' Used to run the simulator '''
101
reactor.callWhenRunning(self._simulator)
103
#---------------------------------------------------------------------------#
104
# Network reset thread
105
#---------------------------------------------------------------------------#
106
# This is linux only, maybe I should make a base class that can be filled
107
# in for linux(debian/redhat)/windows/nix
108
#---------------------------------------------------------------------------#
109
class NetworkReset(Thread):
111
This class is simply a daemon that is spun off at the end of the
112
program to call the network restart function (an easy way to
113
remove all the virtual interfaces)
116
''' Initializes a new instance of the network reset thread '''
117
Thread.__init__(self)
121
''' Run the network reset '''
122
os.system("/etc/init.d/networking restart")
124
#---------------------------------------------------------------------------#
126
#---------------------------------------------------------------------------#
127
class SimulatorFrame(wx.Frame):
129
This class implements the GUI for the flasher application
135
def __init__(self, parent, id, title):
137
Sets up the gui, callback, and widget handles
139
wx.Frame.__init__(self, parent, id, title)
140
wx.EVT_CLOSE(self, self.close_clicked)
142
#---------------------------------------------------------------------------#
144
#---------------------------------------------------------------------------#
145
panel = wx.Panel(self, -1)
146
box = wx.BoxSizer(wx.HORIZONTAL)
147
box.Add(wx.Button(panel, 1, 'Apply'), 1)
148
box.Add(wx.Button(panel, 2, 'Help'), 1)
149
box.Add(wx.Button(panel, 3, 'Close'), 1)
152
#---------------------------------------------------------------------------#
154
#---------------------------------------------------------------------------#
155
#self.tdevice = self.tree.get_widget("fileTxt")
156
#self.tsubnet = self.tree.get_widget("addressTxt")
157
#self.tnumber = self.tree.get_widget("deviceTxt")
159
#---------------------------------------------------------------------------#
161
#---------------------------------------------------------------------------#
162
self.Bind(wx.EVT_BUTTON, self.start_clicked, id=1)
163
self.Bind(wx.EVT_BUTTON, self.help_clicked, id=2)
164
self.Bind(wx.EVT_BUTTON, self.close_clicked, id=3)
167
# self.error_dialog("This program must be run with root permissions!", True)
169
#---------------------------------------------------------------------------#
171
#---------------------------------------------------------------------------#
172
# Not callbacks, but used by them
173
#---------------------------------------------------------------------------#
174
def show_buttons(self, state=False, all=0):
175
''' Greys out the buttons '''
177
self.window.set_sensitive(state)
178
self.bstart.set_sensitive(state)
179
self.tdevice.set_sensitive(state)
180
self.tsubnet.set_sensitive(state)
181
self.tnumber.set_sensitive(state)
183
def destroy_interfaces(self):
184
''' This is used to reset the virtual interfaces '''
189
def error_dialog(self, message, quit=False):
190
''' Quick pop-up for error messages '''
191
log.debug("error event called")
192
dialog = wx.MessageDialog(self, message, 'Error',
193
wx.OK | wx.ICON_ERROR)
195
if quit: self.Destroy()
198
#---------------------------------------------------------------------------#
200
#---------------------------------------------------------------------------#
201
# These are all callbacks for the various buttons
202
#---------------------------------------------------------------------------#
203
def start_clicked(self, widget):
204
''' Starts the simulator '''
208
# check starting network
209
net = self.tsubnet.get_text()
210
octets = net.split('.')
212
base = "%s.%s" % (octets[0], octets[1])
213
net = int(octets[2]) % 255
214
start = int(octets[3]) % 255
216
self.error_dialog("Invalid starting address!");
219
# check interface size
220
size = int(self.tnumber.get_text())
222
for i in range(start, (size + start)):
224
cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j)
226
if j == 254: net = net + 1
229
self.error_dialog("Invalid number of devices!");
233
if os.path.exists(self.file):
234
self.show_buttons(state=False)
236
handle = Simulator(config=self.file)
238
except ConfigurationException, ex:
239
self.error_dialog("Error %s" % ex)
240
self.show_buttons(state=True)
242
self.error_dialog("Device to emulate does not exist!");
245
def help_clicked(self, widget):
246
''' Quick pop-up for about page '''
247
data = gtk.AboutDialog()
248
data.set_version("0.1")
249
data.set_name(('Modbus Simulator'))
250
data.set_authors(["Galen Collins"])
251
data.set_comments(('First Select a device to simulate,\n'
252
+ 'then select the starting subnet of the new devices\n'
253
+ 'then select the number of device to simulate and click start'))
254
data.set_website("http://code.google.com/p/pymodbus/")
255
data.connect("response", lambda w,r: w.hide())
258
def close_clicked(self, event):
259
''' Callback for close button '''
260
log.debug("close event called")
263
def file_changed(self, event):
264
''' Callback for the filename change '''
265
self.file = widget.get_filename()
267
class SimulatorApp(wx.App):
268
''' The main wx application handle for our simulator
272
''' Called by wxWindows to initialize our application
274
:returns: Always True
276
log.debug("application initialize event called")
277
reactor.registerWxApp(self)
278
frame = SimulatorFrame(None, -1, "Pymodbus Simulator")
279
frame.CenterOnScreen()
281
self.SetTopWindow(frame)
284
#---------------------------------------------------------------------------#
285
# Main handle function
286
#---------------------------------------------------------------------------#
287
# This is called when the application is run from a console
288
# We simply start the gui and start the twisted event loop
289
#---------------------------------------------------------------------------#
292
Main control function
293
This either launches the gui or runs the command line application
298
log.setLevel(logging.DEBUG)
299
logging.basicConfig()
301
print "Logging is not supported on this system"
302
simulator = SimulatorApp(0)
305
#---------------------------------------------------------------------------#
306
# Library/Console Test
307
#---------------------------------------------------------------------------#
308
# If this is called from console, we start main
309
#---------------------------------------------------------------------------#
310
if __name__ == "__main__":