1 /* jSSC (Java Simple Serial Connector) - serial port communication library.
2  * (C) Alexey Sokolov (scream3r), 2010-2014.
3  *
4  * Patched for Arduino by Cristian Maglie.
5  *
6  * This file is part of jSSC.
7  *
8  * jSSC is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * jSSC is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with jSSC.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  * If you use jSSC in public project you can inform me about this by e-mail,
22  * of course if you want it.
23  *
24  * e-mail: scream3r.org@gmail.com
25  * web-site: http://scream3r.org | http://code.google.com/p/java-simple-serial-connector/
26  */
27 package processing.app;
28 
29 import java.io.File;
30 import java.util.Comparator;
31 import java.util.TreeSet;
32 import java.util.regex.Pattern;
33 
34 import jssc.SerialNativeInterface;
35 
36 /**
37  *
38  * @author scream3r
39  */
40 public class SerialPortList {
41 
42     private static SerialNativeInterface serialInterface;
43     private static final Pattern PORTNAMES_REGEXP;
44     private static final String PORTNAMES_PATH;
45 
46     static {
47         serialInterface = new SerialNativeInterface();
48         switch (SerialNativeInterface.getOsType()) {
49             case SerialNativeInterface.OS_LINUX: {
50                 PORTNAMES_REGEXP = Pattern.compile("(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}");
51                 PORTNAMES_PATH = "/dev/";
52                 break;
53             }
54             case SerialNativeInterface.OS_SOLARIS: {
55                 PORTNAMES_REGEXP = Pattern.compile("[0-9]*|[a-z]*");
56                 PORTNAMES_PATH = "/dev/term/";
57                 break;
58             }
59             case SerialNativeInterface.OS_MAC_OS_X: {
60                 PORTNAMES_REGEXP = Pattern.compile("(tty|cu)\\..*");
61                 PORTNAMES_PATH = "/dev/";
62                 break;
63             }
64             case SerialNativeInterface.OS_WINDOWS: {
65                 PORTNAMES_REGEXP = Pattern.compile("");
66                 PORTNAMES_PATH = "";
67                 break;
68             }
69             default: {
70                 PORTNAMES_REGEXP = null;
71                 PORTNAMES_PATH = null;
72                 break;
73             }
74         }
75     }
76 
77     //since 2.1.0 -> Fully rewrited port name comparator
78     private static final Comparator<String> PORTNAMES_COMPARATOR = new Comparator<String>() {
79 
80         @Override
81         public int compare(String valueA, String valueB) {
82 
83             if(valueA.equalsIgnoreCase(valueB)){
84                 return valueA.compareTo(valueB);
85             }
86 
87             int minLength = Math.min(valueA.length(), valueB.length());
88 
89             int shiftA = 0;
90             int shiftB = 0;
91 
92             for(int i = 0; i < minLength; i++){
93                 char charA = valueA.charAt(i - shiftA);
94                 char charB = valueB.charAt(i - shiftB);
95                 if(charA != charB){
96                     if(Character.isDigit(charA) && Character.isDigit(charB)){
97                         int[] resultsA = getNumberAndLastIndex(valueA, i - shiftA);
98                         int[] resultsB = getNumberAndLastIndex(valueB, i - shiftB);
99 
100                         if(resultsA[0] != resultsB[0]){
101                             return resultsA[0] - resultsB[0];
102                         }
103 
104                         if(valueA.length() < valueB.length()){
105                             i = resultsA[1];
106                             shiftB = resultsA[1] - resultsB[1];
107                         }
108                         else {
109                             i = resultsB[1];
110                             shiftA = resultsB[1] - resultsA[1];
111                         }
112                     }
113                     else {
114                         if(Character.toLowerCase(charA) - Character.toLowerCase(charB) != 0){
115                             return Character.toLowerCase(charA) - Character.toLowerCase(charB);
116                         }
117                     }
118                 }
119             }
120             return valueA.compareToIgnoreCase(valueB);
121         }
122 
123         /**
124          * Evaluate port <b>index/number</b> from <b>startIndex</b> to the number end. For example:
125          * for port name <b>serial-123-FF</b> you should invoke this method with <b>startIndex = 7</b>
126          *
127          * @return If port <b>index/number</b> correctly evaluated it value will be returned<br>
128          * <b>returnArray[0] = index/number</b><br>
129          * <b>returnArray[1] = stopIndex</b><br>
130          *
131          * If incorrect:<br>
132          * <b>returnArray[0] = -1</b><br>
133          * <b>returnArray[1] = startIndex</b><br>
134          *
135          * For this name <b>serial-123-FF</b> result is:
136          * <b>returnArray[0] = 123</b><br>
137          * <b>returnArray[1] = 10</b><br>
138          */
139         private int[] getNumberAndLastIndex(String str, int startIndex) {
140             String numberValue = "";
141             int[] returnValues = {-1, startIndex};
142             for(int i = startIndex; i < str.length(); i++){
143                 returnValues[1] = i;
144                 char c = str.charAt(i);
145                 if(Character.isDigit(c)){
146                     numberValue += c;
147                 }
148                 else {
149                     break;
150                 }
151             }
152             try {
153                 returnValues[0] = Integer.valueOf(numberValue);
154             }
155             catch (Exception ex) {
156                 //Do nothing
157             }
158             return returnValues;
159         }
160     };
161     //<-since 2.1.0
162 
163     /**
164      * Get sorted array of serial ports in the system using default settings:<br>
165      *
166      * <b>Search path</b><br>
167      * Windows - ""(always ignored)<br>
168      * Linux - "/dev/"<br>
169      * Solaris - "/dev/term/"<br>
170      * MacOSX - "/dev/"<br>
171      *
172      * <b>RegExp</b><br>
173      * Windows - ""<br>
174      * Linux - "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm)[0-9]{1,3}"<br>
175      * Solaris - "[0-9]*|[a-z]*"<br>
176      * MacOSX - "tty.(serial|usbserial|usbmodem).*"<br>
177      *
178      * @return String array. If there is no ports in the system String[]
179      * with <b>zero</b> length will be returned (since jSSC-0.8 in previous versions null will be returned)
180      */
getPortNames()181     public static String[] getPortNames() {
182         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR);
183     }
184 
185     /**
186      * Get sorted array of serial ports in the system located on searchPath
187      *
188      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
189      * The default search paths:<br>
190      * Linux, MacOSX: <b>/dev/</b><br>
191      * Solaris: <b>/dev/term/</b><br>
192      * Windows: <b>this parameter ingored</b>
193      *
194      * @return String array. If there is no ports in the system String[]
195      *
196      * @since 2.3.0
197      */
getPortNames(String searchPath)198     public static String[] getPortNames(String searchPath) {
199         return getPortNames(searchPath, PORTNAMES_REGEXP, PORTNAMES_COMPARATOR);
200     }
201 
202     /**
203      * Get sorted array of serial ports in the system matched pattern
204      *
205      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
206      *
207      * @return String array. If there is no ports in the system String[]
208      *
209      * @since 2.3.0
210      */
getPortNames(Pattern pattern)211     public static String[] getPortNames(Pattern pattern) {
212         return getPortNames(PORTNAMES_PATH, pattern, PORTNAMES_COMPARATOR);
213     }
214 
215     /**
216      * Get sorted array of serial ports in the system matched pattern
217      *
218      * @param comparator Comparator for sotring port names <b>(not null)</b>
219      *
220      * @return String array. If there is no ports in the system String[]
221      *
222      * @since 2.3.0
223      */
getPortNames(Comparator<String> comparator)224     public static String[] getPortNames(Comparator<String> comparator) {
225         return getPortNames(PORTNAMES_PATH, PORTNAMES_REGEXP, comparator);
226     }
227 
228     /**
229      * Get sorted array of serial ports in the system located on searchPath, matched pattern
230      *
231      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
232      * The default search paths:<br>
233      * Linux, MacOSX: <b>/dev/</b><br>
234      * Solaris: <b>/dev/term/</b><br>
235      * Windows: <b>this parameter ingored</b>
236      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
237      *
238      * @return String array. If there is no ports in the system String[]
239      *
240      * @since 2.3.0
241      */
getPortNames(String searchPath, Pattern pattern)242     public static String[] getPortNames(String searchPath, Pattern pattern) {
243         return getPortNames(searchPath, pattern, PORTNAMES_COMPARATOR);
244     }
245 
246     /**
247      * Get sorted array of serial ports in the system located on searchPath and sorted by comparator
248      *
249      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
250      * The default search paths:<br>
251      * Linux, MacOSX: <b>/dev/</b><br>
252      * Solaris: <b>/dev/term/</b><br>
253      * Windows: <b>this parameter ingored</b>
254      * @param comparator Comparator for sotring port names <b>(not null)</b>
255      *
256      * @return String array. If there is no ports in the system String[]
257      *
258      * @since 2.3.0
259      */
getPortNames(String searchPath, Comparator<String> comparator)260     public static String[] getPortNames(String searchPath, Comparator<String> comparator) {
261         return getPortNames(searchPath, PORTNAMES_REGEXP, comparator);
262     }
263 
264     /**
265      * Get sorted array of serial ports in the system matched pattern and sorted by comparator
266      *
267      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
268      * @param comparator Comparator for sotring port names <b>(not null)</b>
269      *
270      * @return String array. If there is no ports in the system String[]
271      *
272      * @since 2.3.0
273      */
getPortNames(Pattern pattern, Comparator<String> comparator)274     public static String[] getPortNames(Pattern pattern, Comparator<String> comparator) {
275         return getPortNames(PORTNAMES_PATH, pattern, comparator);
276     }
277 
278     /**
279      * Get sorted array of serial ports in the system located on searchPath, matched pattern and sorted by comparator
280      *
281      * @param searchPath Path for searching serial ports <b>(not null)</b><br>
282      * The default search paths:<br>
283      * Linux, MacOSX: <b>/dev/</b><br>
284      * Solaris: <b>/dev/term/</b><br>
285      * Windows: <b>this parameter ingored</b>
286      * @param pattern RegExp pattern for matching port names <b>(not null)</b>
287      * @param comparator Comparator for sotring port names <b>(not null)</b>
288      *
289      * @return String array. If there is no ports in the system String[]
290      *
291      * @since 2.3.0
292      */
getPortNames(String searchPath, Pattern pattern, Comparator<String> comparator)293     public static String[] getPortNames(String searchPath, Pattern pattern, Comparator<String> comparator) {
294         if(searchPath == null || pattern == null || comparator == null){
295             return new String[]{};
296         }
297         if(SerialNativeInterface.getOsType() == SerialNativeInterface.OS_WINDOWS){
298             return getWindowsPortNames(pattern, comparator);
299         }
300         return getUnixBasedPortNames(searchPath, pattern, comparator);
301     }
302 
303     /**
304      * Get serial port names in Windows
305      *
306      * @since 2.3.0
307      */
getWindowsPortNames(Pattern pattern, Comparator<String> comparator)308     private static String[] getWindowsPortNames(Pattern pattern, Comparator<String> comparator) {
309         String[] portNames = serialInterface.getSerialPortNames();
310         if(portNames == null){
311             return new String[]{};
312         }
313         TreeSet<String> ports = new TreeSet<>(comparator);
314         for(String portName : portNames){
315             if(pattern.matcher(portName).find()){
316                 ports.add(portName);
317             }
318         }
319         return ports.toArray(new String[ports.size()]);
320     }
321 
322     /**
323      * Universal method for getting port names of _nix based systems
324      */
getUnixBasedPortNames(String searchPath, Pattern pattern, Comparator<String> comparator)325     private static String[] getUnixBasedPortNames(String searchPath, Pattern pattern, Comparator<String> comparator) {
326         searchPath = (searchPath.equals("") ? searchPath : (searchPath.endsWith("/") ? searchPath : searchPath + "/"));
327         String[] returnArray = new String[]{};
328         File dir = new File(searchPath);
329         if(dir.exists() && dir.isDirectory()){
330             File[] files = dir.listFiles();
331             if(files.length > 0){
332                 TreeSet<String> portsTree = new TreeSet<>(comparator);
333                 for(File file : files){
334                     String fileName = file.getName();
335                     if(!file.isDirectory() && !file.isFile() && pattern.matcher(fileName).find()){
336                         String portName = searchPath + fileName;
337                         // For linux ttyS0..31 serial ports check existence by opening each of them
338                         if (fileName.startsWith("ttyS")) {
339 	                        long portHandle = serialInterface.openPort(portName, false);//Open port without TIOCEXCL
340 	                        if(portHandle < 0 && portHandle != SerialNativeInterface.ERR_PORT_BUSY){
341 	                            continue;
342 	                        }
343 	                        else if(portHandle != SerialNativeInterface.ERR_PORT_BUSY) {
344 	                            serialInterface.closePort(portHandle);
345 	                        }
346                         }
347                         portsTree.add(portName);
348                     }
349                 }
350                 returnArray = portsTree.toArray(returnArray);
351             }
352         }
353         return returnArray;
354     }
355 }
356