1#!jython
2#
3# Backend Jython with JavaComm
4#
5# This file is part of pySerial. https://github.com/pyserial/pyserial
6# (C) 2002-2015 Chris Liechti <cliechti@gmx.net>
7#
8# SPDX-License-Identifier:    BSD-3-Clause
9
10from __future__ import absolute_import
11
12from serial.serialutil import *
13
14
15def my_import(name):
16    mod = __import__(name)
17    components = name.split('.')
18    for comp in components[1:]:
19        mod = getattr(mod, comp)
20    return mod
21
22
23def detect_java_comm(names):
24    """try given list of modules and return that imports"""
25    for name in names:
26        try:
27            mod = my_import(name)
28            mod.SerialPort
29            return mod
30        except (ImportError, AttributeError):
31            pass
32    raise ImportError("No Java Communications API implementation found")
33
34
35# Java Communications API implementations
36# http://mho.republika.pl/java/comm/
37
38comm = detect_java_comm([
39    'javax.comm',  # Sun/IBM
40    'gnu.io',      # RXTX
41])
42
43
44def device(portnumber):
45    """Turn a port number into a device name"""
46    enum = comm.CommPortIdentifier.getPortIdentifiers()
47    ports = []
48    while enum.hasMoreElements():
49        el = enum.nextElement()
50        if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL:
51            ports.append(el)
52    return ports[portnumber].getName()
53
54
55class Serial(SerialBase):
56    """\
57    Serial port class, implemented with Java Communications API and
58    thus usable with jython and the appropriate java extension.
59    """
60
61    def open(self):
62        """\
63        Open port with current settings. This may throw a SerialException
64        if the port cannot be opened.
65        """
66        if self._port is None:
67            raise SerialException("Port must be configured before it can be used.")
68        if self.is_open:
69            raise SerialException("Port is already open.")
70        if type(self._port) == type(''):      # strings are taken directly
71            portId = comm.CommPortIdentifier.getPortIdentifier(self._port)
72        else:
73            portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port))     # numbers are transformed to a comport id obj
74        try:
75            self.sPort = portId.open("python serial module", 10)
76        except Exception as msg:
77            self.sPort = None
78            raise SerialException("Could not open port: %s" % msg)
79        self._reconfigurePort()
80        self._instream = self.sPort.getInputStream()
81        self._outstream = self.sPort.getOutputStream()
82        self.is_open = True
83
84    def _reconfigurePort(self):
85        """Set communication parameters on opened port."""
86        if not self.sPort:
87            raise SerialException("Can only operate on a valid port handle")
88
89        self.sPort.enableReceiveTimeout(30)
90        if self._bytesize == FIVEBITS:
91            jdatabits = comm.SerialPort.DATABITS_5
92        elif self._bytesize == SIXBITS:
93            jdatabits = comm.SerialPort.DATABITS_6
94        elif self._bytesize == SEVENBITS:
95            jdatabits = comm.SerialPort.DATABITS_7
96        elif self._bytesize == EIGHTBITS:
97            jdatabits = comm.SerialPort.DATABITS_8
98        else:
99            raise ValueError("unsupported bytesize: %r" % self._bytesize)
100
101        if self._stopbits == STOPBITS_ONE:
102            jstopbits = comm.SerialPort.STOPBITS_1
103        elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
104            jstopbits = comm.SerialPort.STOPBITS_1_5
105        elif self._stopbits == STOPBITS_TWO:
106            jstopbits = comm.SerialPort.STOPBITS_2
107        else:
108            raise ValueError("unsupported number of stopbits: %r" % self._stopbits)
109
110        if self._parity == PARITY_NONE:
111            jparity = comm.SerialPort.PARITY_NONE
112        elif self._parity == PARITY_EVEN:
113            jparity = comm.SerialPort.PARITY_EVEN
114        elif self._parity == PARITY_ODD:
115            jparity = comm.SerialPort.PARITY_ODD
116        elif self._parity == PARITY_MARK:
117            jparity = comm.SerialPort.PARITY_MARK
118        elif self._parity == PARITY_SPACE:
119            jparity = comm.SerialPort.PARITY_SPACE
120        else:
121            raise ValueError("unsupported parity type: %r" % self._parity)
122
123        jflowin = jflowout = 0
124        if self._rtscts:
125            jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN
126            jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT
127        if self._xonxoff:
128            jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN
129            jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT
130
131        self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity)
132        self.sPort.setFlowControlMode(jflowin | jflowout)
133
134        if self._timeout >= 0:
135            self.sPort.enableReceiveTimeout(int(self._timeout*1000))
136        else:
137            self.sPort.disableReceiveTimeout()
138
139    def close(self):
140        """Close port"""
141        if self.is_open:
142            if self.sPort:
143                self._instream.close()
144                self._outstream.close()
145                self.sPort.close()
146                self.sPort = None
147            self.is_open = False
148
149    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
150
151    @property
152    def in_waiting(self):
153        """Return the number of characters currently in the input buffer."""
154        if not self.sPort:
155            raise PortNotOpenError()
156        return self._instream.available()
157
158    def read(self, size=1):
159        """\
160        Read size bytes from the serial port. If a timeout is set it may
161        return less characters as requested. With no timeout it will block
162        until the requested number of bytes is read.
163        """
164        if not self.sPort:
165            raise PortNotOpenError()
166        read = bytearray()
167        if size > 0:
168            while len(read) < size:
169                x = self._instream.read()
170                if x == -1:
171                    if self.timeout >= 0:
172                        break
173                else:
174                    read.append(x)
175        return bytes(read)
176
177    def write(self, data):
178        """Output the given string over the serial port."""
179        if not self.sPort:
180            raise PortNotOpenError()
181        if not isinstance(data, (bytes, bytearray)):
182            raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
183        self._outstream.write(data)
184        return len(data)
185
186    def reset_input_buffer(self):
187        """Clear input buffer, discarding all that is in the buffer."""
188        if not self.sPort:
189            raise PortNotOpenError()
190        self._instream.skip(self._instream.available())
191
192    def reset_output_buffer(self):
193        """\
194        Clear output buffer, aborting the current output and
195        discarding all that is in the buffer.
196        """
197        if not self.sPort:
198            raise PortNotOpenError()
199        self._outstream.flush()
200
201    def send_break(self, duration=0.25):
202        """Send break condition. Timed, returns to idle state after given duration."""
203        if not self.sPort:
204            raise PortNotOpenError()
205        self.sPort.sendBreak(duration*1000.0)
206
207    def _update_break_state(self):
208        """Set break: Controls TXD. When active, to transmitting is possible."""
209        if self.fd is None:
210            raise PortNotOpenError()
211        raise SerialException("The _update_break_state function is not implemented in java.")
212
213    def _update_rts_state(self):
214        """Set terminal status line: Request To Send"""
215        if not self.sPort:
216            raise PortNotOpenError()
217        self.sPort.setRTS(self._rts_state)
218
219    def _update_dtr_state(self):
220        """Set terminal status line: Data Terminal Ready"""
221        if not self.sPort:
222            raise PortNotOpenError()
223        self.sPort.setDTR(self._dtr_state)
224
225    @property
226    def cts(self):
227        """Read terminal status line: Clear To Send"""
228        if not self.sPort:
229            raise PortNotOpenError()
230        self.sPort.isCTS()
231
232    @property
233    def dsr(self):
234        """Read terminal status line: Data Set Ready"""
235        if not self.sPort:
236            raise PortNotOpenError()
237        self.sPort.isDSR()
238
239    @property
240    def ri(self):
241        """Read terminal status line: Ring Indicator"""
242        if not self.sPort:
243            raise PortNotOpenError()
244        self.sPort.isRI()
245
246    @property
247    def cd(self):
248        """Read terminal status line: Carrier Detect"""
249        if not self.sPort:
250            raise PortNotOpenError()
251        self.sPort.isCD()
252