diff --git a/src/main/java/modbuspal/automation/AutomationPanel.java b/src/main/java/modbuspal/automation/AutomationPanel.java index 3e88ccb..f70f69b 100644 --- a/src/main/java/modbuspal/automation/AutomationPanel.java +++ b/src/main/java/modbuspal/automation/AutomationPanel.java @@ -279,20 +279,20 @@ public void windowDeactivated(WindowEvent e) { } - @Override public void automationHasEnded(Automation source) { //playToggleButton.setText("Start"); playToggleButton.setSelected(false); setBackground(false); + automation.stop(); } - @Override public void automationHasStarted(Automation aThis) { //playToggleButton.setText("Stop"); playToggleButton.setSelected(true); setBackground(true); + automation.start(); } @Override diff --git a/src/main/java/modbuspal/help/modbus-slaves-add-tcp.html b/src/main/java/modbuspal/help/modbus-slaves-add-tcp.html index bf5d491..d65f020 100644 --- a/src/main/java/modbuspal/help/modbus-slaves-add-tcp.html +++ b/src/main/java/modbuspal/help/modbus-slaves-add-tcp.html @@ -28,11 +28,11 @@

Regular MODBUS TCP addressing

192.168.10.100
Create just one slave that will only reply to requests received on the network interface whose IP address is 192.168.10.100.
-
10.23.6.2, 10.23.6.8
+
10.23.6.2, 10.23.6.8 (not implemented)
Create 2 slaves that will reply to requests received on network interfaces whose IP addresses are 10.23.6.2, and 10.23.6.8.
-
10.23.6.2-10.23.6.8
+
10.23.6.2-10.23.6.8 (not implemented)
Create 9 slaves that will reply to requests received on network interfaces whose IP addresses are 10.23.6.2, 10.23.6.3, ..., 10.23.6.7 and 10.23.6.8.
@@ -53,10 +53,10 @@

Multiple slaves sharing the same IP address

following patterns:

-
127.0.0.1(17)
+
127.0.0.1(17) (not implemented)
Create juste one slave at IP address 127.0.0.1 (listen on all network interfaces) and slave number 17.
-
192.168.10.100(10-15)
+
192.168.10.100(10-15) (not implemented)
Create 6 slaves sharing the same IP address (local network interface 192.168.10.100). Each slave is assigned a slave number ranging from 10 to 15 included.
diff --git a/src/main/java/modbuspal/main/ModbusPalGui.java b/src/main/java/modbuspal/main/ModbusPalGui.java index 3bb567a..b9e2d65 100644 --- a/src/main/java/modbuspal/main/ModbusPalGui.java +++ b/src/main/java/modbuspal/main/ModbusPalGui.java @@ -30,9 +30,56 @@ public class ModbusPalGui { private static final HashMap instances = new HashMap(); + public static final int MAX_PORT_NUMBER = 65536; + + private static String initialLoadFilePath = ""; + private static int initialPortNumber = -1; + + /** + * This method will display the help message to the console and exit the software. + */ + public static void displayHelpMessage() + { + System.out.println( "This software launches the Modbus Slave simulation program: ModbusPal." ); + System.out.println( "Arguments in this program include:" ); + System.out.println( + "-install (optional): This flag is to tell program to install itself if it is not installed." ); + System.out.println( "-loadFile (optional): This argument is to load a project file at launch. Example usage:" ); + System.out.println( "\t-loadFile=\"your/absolute/path/name/example.xmpp\"" ); + System.out.println( "Make sure the path name is the absolute path, or you will get an error message back." ); + System.out.println( + "-portNumber (optional): This argument sets the initial TCP/IP port number to a number between 0 and " + + MAX_PORT_NUMBER + ". Example usage:" ); + System.out.println( "\t-portNumber=1234" ); + System.out.println( + "Make sure the port you choose is not a reserved port number or in use. If a number is not given, an error message will be returned." ); + System.out.println( + "If this argument is not given or an invalid port value is given, then the port number will be set to " + + ModbusPalPane.DEFAULT_PORT_TEXT + ", or the value in the initial project file loaded." ); + System.out.println( "-help (optional): Displays the help message." ); + System.exit( 0 ); + } + + /** + * This method gets the initial load file path to load specified by the user in the command line arguments. + * @return {String} The absolute initial load file path. Returns "" if no argument was given. + */ + public static String getInitialLoadFilePath() + { + return initialLoadFilePath; + } + + /** + * This method gets the initial port number to load specified by the user in the command line arguments. + * @return {int} The initial port number for TCP/IP connections. Returns -1 if no port number was given. + */ + public static int getInitialPortNumber() + { + return initialPortNumber; + } /** - * this method will try to change the Look and Feel of the applcation, + * this method will try to change the Look and Feel of the application, * using the system l&f. It means that the application will get the Windows * l&f on Windows, etc... */ @@ -55,31 +102,48 @@ public static void install() } /** - * @param args the command line arguments + * @param {String[]} args The command line arguments */ public static void main(String args[]) { boolean runInstall = false; boolean runGui = true; + String installArgFlag = "-install"; + String loadFileArgFlag = "-loadFile="; + String portNumberArgFlag = "-portNumber="; - if( args.length>=1 ) + if( args.length >= 1 ) { for(String arg:args) { - if( arg.compareToIgnoreCase("-install")==0 ) + if( arg.startsWith( installArgFlag ) ) { runInstall = true; runGui = false; } + else if( arg.startsWith( loadFileArgFlag ) ) + { + initialLoadFilePath = arg.substring( arg.lastIndexOf( loadFileArgFlag ) + loadFileArgFlag.length() ); + } + else if( arg.startsWith( portNumberArgFlag ) ) + { + String portNumberString = arg.substring( arg.lastIndexOf( portNumberArgFlag ) + portNumberArgFlag.length() ); + initialPortNumber = Integer.valueOf( portNumberString ).intValue(); + } + else + { + displayHelpMessage(); + } } } + if( runInstall == true ) { install(); } if( runGui == true ) - { + { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -88,12 +152,8 @@ public void run() { } }); } - - } - - /** * A JinternalFrame that contains a ModbusPalPane. */ diff --git a/src/main/java/modbuspal/main/ModbusPalPane.java b/src/main/java/modbuspal/main/ModbusPalPane.java index cb5a4ce..9938864 100644 --- a/src/main/java/modbuspal/main/ModbusPalPane.java +++ b/src/main/java/modbuspal/main/ModbusPalPane.java @@ -53,6 +53,9 @@ public class ModbusPalPane /** Base registry key for the configuration of the application. */ public static final String BASE_REGISTRY_KEY = "modbuspal"; + + /** Default TCP/IP port in a string to be loaded into the GUI. */ + public static final String DEFAULT_PORT_TEXT = "502"; private ArrayList listeners = new ArrayList(); @@ -284,12 +287,66 @@ public ModbusPalPane(boolean useInternalConsole) installRecorder(); installCommPorts(); //installScriptEngine(); - - setProject( new ModbusPalProject() ); - + + String initialLoadProjectFilePath = ModbusPalGui.getInitialLoadFilePath(); + ModbusPalProject project = null; + File fileCheck = new File( initialLoadProjectFilePath ); + if( initialLoadProjectFilePath != "" && fileCheck.isFile() ) + { + try + { + System.out.println( "Loading the project file: " + initialLoadProjectFilePath ); + project = loadProject( initialLoadProjectFilePath ); + + // Need to initialize the port number after loading the project, and not every time we load or set a project because if the + // user wants to load another file, we don't want to clobber it with the command line initial port number. + // This call must be called before the runToggleButton.doClick() method is invoked. + initializeInitialPortNumber( project ); + + // Now that we have loaded a project from an initial project file, it is time to start all of the + // automations that have been loaded from the project file. + Component panels[] = automationsListPanel.getComponents(); + for( int panelIndex = 0; panelIndex < panels.length; panelIndex++ ) + { + if( panels[ panelIndex ] instanceof AutomationPanel ) + { + AutomationPanel panel = ( AutomationPanel ) panels[ panelIndex ]; + panel.automationHasStarted( null ); + } + } + runToggleButton.doClick(); + } + catch( Exception exception ) + { + System.out.println( + "Could not load the initial project file path \"" + initialLoadProjectFilePath + "\"." ); + System.out.println( "Check the path you inputted into the command line arguments." ); + } + } + else + { + project = new ModbusPalProject(); + setProject( project ); + // Initializing the initial port number in case a port number was given, but no initial load file was given in the + // command line arguments. + initializeInitialPortNumber( project ); + } } - + /** + * Initialize the initial port number given from the command line arguments if there was a valid number given in the command line. + * @param project The current ModbusPal project that is being used. + */ + private void initializeInitialPortNumber( ModbusPalProject project ) + { + int initialPortNumber = ModbusPalGui.getInitialPortNumber(); + if( initialPortNumber >= 0 && initialPortNumber <= ModbusPalGui.MAX_PORT_NUMBER ) + { + project.linkTcpipPort= Integer.toString( initialPortNumber ); + } + portTextField.setText( project.linkTcpipPort ); + } + private void installConsole() { try { @@ -501,8 +558,19 @@ private void initComponents() { gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 2); tcpIpSettingsPanel.add(jLabel1, gridBagConstraints); - portTextField.setText("502"); + int initialPortNumber = ModbusPalGui.getInitialPortNumber(); + if( initialPortNumber >= 0 && initialPortNumber <= ModbusPalGui.MAX_PORT_NUMBER ) + { + System.out.println( "Loading the initial TCP/IP port number: " + initialPortNumber ); + portTextField.setText( Integer.toString( initialPortNumber ) ); + } + else + { + System.out.println( "Could not load an initial port number. Loading default port number." ); + portTextField.setText( DEFAULT_PORT_TEXT ); + } portTextField.setPreferredSize(new java.awt.Dimension(40, 20)); + gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.insets = new java.awt.Insets(5, 2, 5, 5); tcpIpSettingsPanel.add(portTextField, gridBagConstraints); @@ -1314,7 +1382,6 @@ private void loadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI private void addAutomationButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addAutomationButtonActionPerformed - String name = Automation.DEFAULT_NAME + " #" + String.valueOf( modbusPalProject.idGenerator.createID() ); Automation automation = new Automation( name ); modbusPalProject.addAutomation(automation); diff --git a/src/main/java/modbuspal/main/ModbusPalProject.java b/src/main/java/modbuspal/main/ModbusPalProject.java index 82118f6..2dcfdb8 100644 --- a/src/main/java/modbuspal/main/ModbusPalProject.java +++ b/src/main/java/modbuspal/main/ModbusPalProject.java @@ -10,6 +10,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -109,8 +111,6 @@ public ModbusPalProject() generatorFactory.add( new modbuspal.generator.linear.LinearGenerator() ); generatorFactory.add( new modbuspal.generator.random.RandomGenerator() ); generatorFactory.add( new modbuspal.generator.sine.SineGenerator() ); - - } private ModbusPalProject(Document doc, File source) @@ -411,8 +411,11 @@ private void loadBindings(Document doc, ModbusSlave slave) /** * This method will examine the content of a "" tag in order to * parse the attributes it contains, and also the child tags that may exist. - * @param node reference on the Node that represents a "" tag in the + * @param node Reference on the Node that represents a "" tag in the * project. + * @param slave Reference to a ModbusSlave to bind the automation to. If the passed in + * slave is "null", it will retrieve the ModbusSlave that is the parent of this register or coil + * node the automation is being bound to. * @throws java.lang.InstantiationException * @throws java.lang.IllegalAccessException */ @@ -443,21 +446,36 @@ private void loadBinding(Node node, ModbusSlave slave) Node orderNode = attributes.getNamedItem("order"); String orderValue = orderNode.getNodeValue(); int wordOrder = Integer.parseInt(orderValue); + + boolean isRegister = true; // retrieve the register that is the parent of this node - Node parentRegister = XMLTools.findParent(node,"register"); - String parentAddress = XMLTools.getAttribute(ModbusPalXML.XML_ADDRESS_ATTRIBUTE, parentRegister); - int registerAddress = Integer.parseInt( parentAddress ); + Node parentNode = XMLTools.findParent(node,"register"); + String parentAddress = XMLTools.getAttribute(ModbusPalXML.XML_ADDRESS_ATTRIBUTE, parentNode); + + int ioAddress = 0; + if( parentAddress == null ) + { + isRegister = false; + parentNode = XMLTools.findParent(node, "coil"); + parentAddress = XMLTools.getAttribute(ModbusPalXML.XML_ADDRESS_ATTRIBUTE, parentNode); + if( parentAddress == null ) + { + System.out.println( "Cannot bind automation. Parent is neither a register or coil!" ); + return; + } + } + + ioAddress = Integer.parseInt( parentAddress ); - // Instanciate the binding: + // Instantiate the binding: Binding binding = bindingFactory.newInstance(className); binding.setup(automation, wordOrder); - - if( slave==null) + if( slave==null ) { // retrieve the slave that is the parent of this register - Node parentSlave = XMLTools.findParent(parentRegister, "slave"); + Node parentSlave = XMLTools.findParent(parentNode, "slave"); String slaveAddress = XMLTools.getAttribute(ModbusPalXML.XML_SLAVE_ID2_ATTRIBUTE, parentSlave); if( slaveAddress!= null ) @@ -467,15 +485,28 @@ private void loadBinding(Node node, ModbusSlave slave) } else { - slaveAddress = XMLTools.getAttribute(ModbusPalXML.XML_SLAVE_ID_ATTRIBUTE, parentSlave); - int slaveId = Integer.parseInt(slaveAddress); - ModbusSlaveAddress msa = new ModbusSlaveAddress(slaveId); - slave = getModbusSlave(msa); + slaveAddress = XMLTools.getAttribute(ModbusPalXML.XML_SLAVE_ID_ATTRIBUTE, parentSlave); + try + { + ModbusSlaveAddress msa = new ModbusSlaveAddress( InetAddress.getByName( slaveAddress ) ); + slave = getModbusSlave(msa); + } + catch (UnknownHostException exception) + { + System.out.println( "Unable to get Modbus Slave IP address from slave address: " + slaveAddress ); + } } } - // bind the register and the automation - slave.getHoldingRegisters().bind(registerAddress, binding); + // bind the registers, coils, and the automation + if( isRegister ) + { + slave.getHoldingRegisters().bind(ioAddress, binding); + } + else + { + slave.getCoils().bind(ioAddress, binding); + } } diff --git a/src/main/java/modbuspal/slave/ModbusSlave.java b/src/main/java/modbuspal/slave/ModbusSlave.java index 9dc53ee..64855c4 100644 --- a/src/main/java/modbuspal/slave/ModbusSlave.java +++ b/src/main/java/modbuspal/slave/ModbusSlave.java @@ -7,6 +7,8 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -630,8 +632,15 @@ public void load(ModbusPalProject mpp, Node node, boolean importMode) } else { - String id = XMLTools.getAttribute(XML_SLAVE_ID_ATTRIBUTE, node); - slaveId = new ModbusSlaveAddress(Integer.valueOf(id)); + String id = XMLTools.getAttribute(XML_SLAVE_ID_ATTRIBUTE, node); + try + { + slaveId = new ModbusSlaveAddress( InetAddress.getByName( id ) ); + } + catch (UnknownHostException exception) + { + System.out.println( "Unknown host while loading Modbus Slave: " + id ); + } } String en = XMLTools.getAttribute(XML_SLAVE_ENABLED_ATTRIBUTE, node); diff --git a/src/main/java/modbuspal/toolkit/XMLTools.java b/src/main/java/modbuspal/toolkit/XMLTools.java index 801baa5..5c5c70d 100644 --- a/src/main/java/modbuspal/toolkit/XMLTools.java +++ b/src/main/java/modbuspal/toolkit/XMLTools.java @@ -71,6 +71,10 @@ public InputSource resolveEntity(String publicId, String systemId) */ public static String getAttribute(String attr, Node node) { + if( node == null ) + { + return null; + } NamedNodeMap attributes = node.getAttributes(); Node attribute = attributes.getNamedItem(attr); if( attribute==null )