Skip to content

Commit 9370c9a

Browse files
Merge pull request #64 from niolabs/add-timeout
2 parents 44a29dc + 4503031 commit 9370c9a

10 files changed

Lines changed: 102 additions & 38 deletions

File tree

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ python:
44
- "3.4"
55
- "3.5"
66
install:
7-
pip install -e .[tornado]
7+
- if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then travis_retry pip install mock; fi
8+
- pip install -e .[tornado]
89
# command to run tests
910
script: py.test

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@
2929
packages=find_packages(exclude=['tests', '*.tests']),
3030
install_requires=['pyserial'],
3131
extras_require={
32-
'tornado': ['tornado']
32+
'tornado': ['tornado~=4.5']
3333
}
3434
)

xbee/backend/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
class CommandFrameException(KeyError):
1919
pass
2020

21+
class TimeoutException(Exception):
22+
pass
23+
2124

2225
class XBeeBase(object):
2326
"""

xbee/tests/Fake.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def __init__(self, port='/dev/null', baudrate=19200, timeout=1,
2929
self.xonxoff = xonxoff
3030
self.rtscts = rtscts
3131
self._is_open = True
32+
self.fd = 0
3233

3334
self._data_written = ""
3435
self._read_data = ""

xbee/thread/base.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""
1414
from xbee.frame import APIFrame
1515
from xbee.backend.base import XBeeBase as _XBeeBase
16+
from xbee.backend.base import TimeoutException as _TimeoutException
1617
import threading
1718
import time
1819

@@ -93,7 +94,7 @@ def run(self):
9394
if self._error_callback:
9495
self._error_callback(e)
9596

96-
def wait_read_frame(self):
97+
def wait_read_frame(self, timeout=None):
9798
"""
9899
wait_read_frame: None -> frame info dictionary
99100
@@ -102,10 +103,10 @@ def wait_read_frame(self):
102103
wait_read_frame attempts to parse the data contained within it
103104
and returns the resulting dictionary
104105
"""
105-
frame = self._wait_for_frame()
106+
frame = self._wait_for_frame(timeout)
106107
return self._split_response(frame.data)
107108

108-
def _wait_for_frame(self):
109+
def _wait_for_frame(self, timeout=None):
109110
"""
110111
_wait_for_frame: None -> binary data
111112
@@ -119,11 +120,17 @@ def _wait_for_frame(self):
119120
"""
120121
frame = APIFrame(escaped=self._escaped)
121122

123+
deadline = 0
124+
if timeout is not None and timeout > 0:
125+
deadline = time.time() + timeout
126+
122127
while True:
123128
if self._callback and not self._thread_continue:
124129
raise ThreadQuitException
125130

126131
if self.serial.inWaiting() == 0:
132+
if deadline and time.time() > deadline:
133+
raise _TimeoutException
127134
time.sleep(.01)
128135
continue
129136

xbee/tornado/base.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212
from xbee.frame import APIFrame
1313
from xbee.backend.base import XBeeBase as _XBeeBase
14+
from xbee.backend.base import TimeoutException as _TimeoutException
1415
from tornado import ioloop, gen
1516
from tornado.locks import Event
1617
from tornado.concurrent import Future
@@ -45,27 +46,28 @@ class XBeeBase(_XBeeBase):
4546
argument is also used.
4647
"""
4748
def __init__(self, *args, **kwargs):
49+
if 'io_loop' in kwargs:
50+
self._ioloop = kwargs.pop('io_loop')
51+
else:
52+
self._ioloop = ioloop.IOLoop.current()
53+
4854
super(XBeeBase, self).__init__(*args, **kwargs)
4955

5056
self._running = Event()
5157
self._running.set()
5258

53-
if 'io_loop' in kwargs:
54-
self._ioloop = kwargs['io_loop']
55-
else:
56-
self._ioloop = ioloop.IOLoop.current()
57-
5859
self._frame_future = None
5960
self._frame_queue = deque()
6061

6162
if self._callback:
6263
# Make Non-Blocking
6364
self.serial.timeout = 0
64-
self._ioloop.add_handler(self.serial.fd,
65-
self._process_input,
66-
ioloop.IOLoop.READ)
6765
self.process_frames()
6866

67+
self._ioloop.add_handler(self.serial.fd,
68+
self._process_input,
69+
ioloop.IOLoop.READ)
70+
6971
def halt(self):
7072
"""
7173
halt: None -> None
@@ -99,15 +101,25 @@ def process_frames(self):
99101
self._error_callback(e)
100102

101103
@gen.coroutine
102-
def wait_read_frame(self):
103-
frame = yield self._get_frame()
104+
def wait_read_frame(self, timeout=None):
105+
frame = yield self._get_frame(timeout=timeout)
104106
raise gen.Return(self._split_response(frame.data))
105107

106-
def _get_frame(self):
108+
def _get_frame(self, timeout=None):
107109
future = Future()
108110
if self._frame_queue:
109111
future.set_result(self._frame_queue.popleft())
110112
else:
113+
if timeout is not None:
114+
def on_timeout():
115+
future.set_exception(_TimeoutException())
116+
117+
handle = self._ioloop.add_timeout(
118+
self._ioloop.time() + timeout, on_timeout
119+
)
120+
future.add_done_callback(lambda _:
121+
self._ioloop.remove_timeout(handle))
122+
111123
self._frame_future = future
112124

113125
return future

xbee/tornado/tests/test_base.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@
99
"""
1010

1111
import unittest
12+
try:
13+
from unittest.mock import Mock
14+
except ImportError:
15+
from mock import Mock
16+
1217
from xbee.tornado import has_tornado
1318

1419
if not has_tornado:
1520
raise unittest.SkipTest("Requires Tornado")
1621

22+
from tornado import ioloop # noqa
1723
from tornado.testing import AsyncTestCase, gen_test # noqa
1824
from tornado.test.util import unittest # noqa
1925
from xbee.tornado.base import XBeeBase # noqa
@@ -26,14 +32,19 @@ class TestReadFromDevice(AsyncTestCase):
2632
API frame
2733
"""
2834

35+
def setUp(self):
36+
super(TestReadFromDevice, self).setUp()
37+
self._patch_io = ioloop.IOLoop.current()
38+
self._patch_io.add_handler = Mock()
39+
2940
@gen_test
3041
def test_read(self):
3142
"""
3243
_wait_for_frame should properly read a frame of data
3344
"""
3445
device = Serial()
3546
device.set_read_data(b'\x7E\x00\x01\x00\xFF')
36-
xbee = XBeeBase(device)
47+
xbee = XBeeBase(device, io_loop=self._patch_io)
3748

3849
xbee._process_input(None, None)
3950
frame = yield xbee._get_frame()
@@ -46,7 +57,7 @@ def test_read_invalid_followed_by_valid(self):
4657
"""
4758
device = Serial()
4859
device.set_read_data(b'\x7E\x00\x01\x00\xFA' + b'\x7E\x00\x01\x05\xFA')
49-
xbee = XBeeBase(device)
60+
xbee = XBeeBase(device, io_loop=self._patch_io)
5061

5162
xbee._process_input(None, None)
5263
# First process ends with no good frame, process next
@@ -64,7 +75,7 @@ def test_read_escaped(self):
6475
device.set_read_data(
6576
b'\x7E\x00\x04\x7D\x5E\x7D\x5D\x7D\x31\x7D\x33\xE0')
6677

67-
xbee = XBeeBase(device, escaped=True)
78+
xbee = XBeeBase(device, escaped=True, io_loop=self._patch_io)
6879

6980
xbee._process_input(None, None)
7081
frame = yield xbee._get_frame()

xbee/tornado/tests/test_digimesh.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77
Tests the XBee DigiMesh implementation class for API compliance
88
"""
99
import unittest
10+
try:
11+
from unittest.mock import Mock
12+
except ImportError:
13+
from mock import Mock
14+
1015
from xbee.tornado import has_tornado
1116

1217
if not has_tornado:
1318
raise unittest.SkipTest("Requires Tornado")
1419

20+
from tornado import ioloop # noqa
21+
from xbee.tests.Fake import Serial # noqa
1522
from xbee.tornado.digimesh import DigiMesh # noqa
1623

1724

@@ -22,8 +29,11 @@ class TestDigiMesh(unittest.TestCase):
2229
"""
2330

2431
def setUp(self):
25-
self.digimesh = DigiMesh(None)
2632
super(TestDigiMesh, self).setUp()
33+
patch_io = ioloop.IOLoop.current()
34+
patch_io.add_handler = Mock()
35+
serial_port = Serial()
36+
self.digimesh = DigiMesh(serial_port, io_loop=patch_io)
2737

2838
def test_split_tx_status(self):
2939
data = b'\x8b\x01\xff\xff\x01\x01\x01'

0 commit comments

Comments
 (0)