Skip to content

Commit 2fe27aa

Browse files
committed
Restructure code
1 parent 74d9fbc commit 2fe27aa

File tree

6 files changed

+250
-249
lines changed

6 files changed

+250
-249
lines changed

tests/test_tinylink.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_basic(self):
4949
size, tinylink.LEN_PREAMBLE + tinylink.LEN_HEADER +
5050
tinylink.LEN_BODY + len(message))
5151

52-
# Read `size' bytes to receive the full frame, test it partially
52+
# Read `size` bytes to receive the full frame, test it partially.
5353
link.read(1)
5454
link.read(1)
5555
link.read(1)
@@ -86,7 +86,7 @@ def test_sync(self):
8686
handle = DummyHandle()
8787
link = tinylink.TinyLink(handle)
8888

89-
garbage = b"Garbage here that doesn't synchronize."
89+
garbage = b"Garbage here that does not synchronize."
9090
message = b"Hi!"
9191

9292
size = handle.write(garbage) + link.write(message)
@@ -103,7 +103,7 @@ def test_sync_small(self):
103103
handle = DummyHandle()
104104
link = tinylink.TinyLink(handle, max_length=4)
105105

106-
garbage = b"Garbage here that doesn't synchronize."
106+
garbage = b"Garbage here that does not synchronize."
107107
message = b"Hi!"
108108

109109
size = handle.write(garbage) + link.write(message)
@@ -162,7 +162,7 @@ def test_damaged_a(self):
162162

163163
def test_damaged_b(self):
164164
"""
165-
Test damaged frame (header) that won't return anything.
165+
Test damaged frame (header) that will not return anything.
166166
"""
167167

168168
handle = DummyHandle()

tinylink/__init__.py

Lines changed: 3 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -1,232 +1,4 @@
1-
from tinylink import utils
1+
from .consts import * # noqa
2+
from .link import Frame, TinyLink # noqa
23

3-
import struct
4-
import six
5-
6-
__version__ = "1.1"
7-
8-
__all__ = ["Frame", "TinyLink"]
9-
10-
# This can be anything, and is used to synchronize a frame
11-
PREAMBLE = 0xAA55AA55
12-
13-
# Endianness
14-
LITTLE_ENDIAN = "<"
15-
BIG_ENDIAN = ">"
16-
17-
# Protocol states.
18-
WAITING_FOR_PREAMBLE = 1
19-
WAITING_FOR_HEADER = 2
20-
WAITING_FOR_BODY = 3
21-
22-
# Message flags (reserved).
23-
FLAG_NONE = 0x00
24-
FLAG_RESET = 0x01
25-
FLAG_ERROR = 0x02
26-
FLAG_PRIORITY = 0x04
27-
28-
# Don't change these values!
29-
LEN_PREAMBLE = 4
30-
LEN_FLAGS = 2
31-
LEN_LENGTH = 2
32-
LEN_XOR = 1
33-
LEN_CRC = 4
34-
LEN_HEADER = LEN_FLAGS + LEN_LENGTH + LEN_XOR
35-
LEN_BODY = LEN_CRC
36-
37-
38-
class Frame(object):
39-
"""
40-
Represents a frame.
41-
"""
42-
43-
def __init__(self, data=None, flags=FLAG_NONE, damaged=False):
44-
if data is not None:
45-
if not type(data) == six.binary_type:
46-
raise ValueError("Provided data must be encoded as bytes.")
47-
else:
48-
data = bytes()
49-
50-
self.data = data
51-
self.flags = flags
52-
self.damaged = damaged
53-
54-
def __repr__(self):
55-
return "%s(%s, flags=%d, damaged=%s)" % (
56-
self.__class__.__name__, repr(self.data), self.flags, self.damaged)
57-
58-
59-
class TinyLink(object):
60-
"""
61-
TinyLink state machine for streaming communication with low-speed embedded
62-
applications that only use RX/TX. Every message is encapsulated in a frame.
63-
A frame has a header checksum and a frame checksum, to detect errors as
64-
fast as possible (this can happen when you jump right into a stream of
65-
packets, without being synchronized).
66-
67-
A typical frame has 13 bytes overhead, and can have a data payload up to
68-
65536 bytes.
69-
70-
It does not provide error correction and the bytes are not aligned.
71-
"""
72-
73-
def __init__(self, handle, endianness=LITTLE_ENDIAN,
74-
max_length=2**(LEN_LENGTH * 8), ignore_damaged=False):
75-
"""
76-
Construct a new TinyLink state machine. A state machine takes a handle,
77-
which provides a `read' and `write' method.
78-
79-
The endianness is either LITTLE_ENDIAN or BIG_ENDIAN. While big endian
80-
is common for networking, little endian is directly compatible with ARM
81-
microcontrollers, so the microcontrollers don't have to change the
82-
endianness.
83-
84-
Both microcontroller and this instance should agree upon the value of
85-
`max_length'. In case a message is received that exceeds this value, it
86-
will be silently ignored.
87-
88-
By default, if a fully received frame is damaged, it will be returned
89-
as a `DamagedFrame' instance, unless `ignored_damaged' is True.
90-
"""
91-
92-
self.handle = handle
93-
self.endianness = endianness
94-
self.max_length = max_length
95-
self.ignore_damaged = ignore_damaged
96-
97-
# Set initial state
98-
self.state = WAITING_FOR_PREAMBLE
99-
100-
# Pre-allocate buffer that fits header + body. The premable will be
101-
# cleared when it is detected, so it doesn't need space.
102-
self.stream = bytearray(max_length + LEN_HEADER + LEN_BODY)
103-
self.index = 0
104-
105-
# Python 2 does not allow unpack from bytearray, but Python 3.
106-
if six.PY3:
107-
self.buffer = self.stream
108-
else:
109-
self.buffer = buffer(self.stream)
110-
111-
def write_frame(self, frame):
112-
"""
113-
Write a frame via the handle.
114-
"""
115-
116-
result = bytearray()
117-
length = len(frame.data or [])
118-
119-
# Check length of message
120-
if length > self.max_length:
121-
raise ValueError(
122-
"Message length %d exceeds max length %d" % (
123-
length, self.max_length))
124-
125-
# Pack header
126-
checksum_header = utils.checksum_header(frame.flags, length)
127-
result += struct.pack(
128-
self.endianness + "IHHB", PREAMBLE, frame.flags, length,
129-
checksum_header)
130-
131-
# Pack data
132-
if frame.data is not None:
133-
checksum_frame = utils.checksum_frame(frame.data, checksum_header)
134-
result += struct.pack(
135-
self.endianness + str(length) + "sI", frame.data,
136-
checksum_frame)
137-
138-
# Write to file
139-
return self.handle.write(result)
140-
141-
def write(self, data, flags=FLAG_NONE):
142-
"""
143-
Shorthand for `write_frame(Frame(data, flags=flags))'.
144-
"""
145-
146-
return self.write_frame(Frame(data, flags=flags))
147-
148-
def read(self, limit=1):
149-
"""
150-
Read up to `limit' bytes from the handle and process it. Returns a list
151-
of received frames, if any.
152-
"""
153-
154-
# List of frames received
155-
frames = []
156-
157-
# Bytes are added one at a time
158-
while limit:
159-
char = self.handle.read(1)
160-
161-
if not char:
162-
return []
163-
164-
# Append to stream
165-
self.stream[self.index] = ord(char)
166-
self.index += 1
167-
168-
# Decide what to do
169-
if self.state == WAITING_FOR_PREAMBLE:
170-
if self.index >= LEN_PREAMBLE:
171-
start, = struct.unpack_from(
172-
self.endianness + "I", self.buffer, self.index - 4)
173-
174-
if start == PREAMBLE:
175-
# Advance to next state
176-
self.index = 0
177-
self.state = WAITING_FOR_HEADER
178-
elif self.index == self.max_length + LEN_HEADER + LEN_BODY:
179-
# Preamble not found and stream is full. Copy last four
180-
# bytes, because the next byte may form the preamble
181-
# together with the last three bytes.
182-
self.stream[0:4] = self.stream[-4:]
183-
self.index = 4
184-
185-
elif self.state == WAITING_FOR_HEADER:
186-
if self.index == LEN_HEADER:
187-
flags, length, checksum = struct.unpack_from(
188-
self.endianness + "HHB", self.buffer)
189-
190-
# Verify checksum
191-
if checksum == utils.checksum_header(flags, length) and \
192-
length <= self.max_length:
193-
194-
if length > 0:
195-
self.state = WAITING_FOR_BODY
196-
else:
197-
# Frame without body.
198-
frames.append(Frame(flags=flags))
199-
200-
self.index = 0
201-
self.state = WAITING_FOR_PREAMBLE
202-
else:
203-
# Reset to start state
204-
self.index = 0
205-
self.state = WAITING_FOR_PREAMBLE
206-
207-
elif self.state == WAITING_FOR_BODY:
208-
# Unpack header
209-
flags, length, checksum_a = struct.unpack_from(
210-
self.endianness + "HHB", self.buffer)
211-
212-
if self.index == LEN_HEADER + length + LEN_CRC:
213-
# Unpack body
214-
result, checksum_b = struct.unpack_from(
215-
self.endianness + str(length) + "sI",
216-
self.buffer, LEN_HEADER)
217-
218-
# Verify checksum
219-
if checksum_b == utils.checksum_frame(result, checksum_a):
220-
frames.append(Frame(result, flags=flags))
221-
elif not self.ignore_damaged:
222-
frames.append(Frame(result, flags=flags, damaged=True))
223-
224-
# Reset to start state
225-
self.index = 0
226-
self.state = WAITING_FOR_PREAMBLE
227-
228-
# Decrement number of bytes to read
229-
limit -= 1
230-
231-
# Done
232-
return frames
4+
__version__ = "2.0.0"

tinylink/cli.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def parse_arguments():
3131

3232
parser = argparse.ArgumentParser()
3333

34-
# Add option
34+
# Add options.
3535
parser.add_argument("port", type=str, help="serial port")
3636
parser.add_argument(
3737
"baudrate", type=int, default=9600, help="serial baudrate")
@@ -41,7 +41,7 @@ def parse_arguments():
4141
"--endianness", type=str, default="little", choices=["big", "little"],
4242
help="maximum length of frame")
4343

44-
# Parse command line
44+
# Parse command line.
4545
return parser.parse_args(), parser
4646

4747

@@ -70,7 +70,7 @@ def dump(prefix, data):
7070

7171
result.append(prefix + " " + hexstr + bytestr.decode("ascii"))
7272

73-
# Return concatenated string
73+
# Return concatenated string.
7474
return "\n".join(result)
7575

7676

@@ -81,7 +81,7 @@ def process_link(link):
8181

8282
frames = link.read()
8383

84-
# Print received frames
84+
# Print received frames.
8585
for frame in frames:
8686
sys.stdout.write("### Type = %s\n" % frame.__class__.__name__)
8787
sys.stdout.write("### Flags = 0x%04x\n" % frame.flags)
@@ -175,46 +175,46 @@ def main():
175175
"install this first.\n")
176176
return 1
177177

178-
# Parse arguments
178+
# Parse arguments.
179179
arguments, parser = parse_arguments()
180180

181181
if arguments.endianness == "little":
182182
endianness = tinylink.LITTLE_ENDIAN
183183
else:
184184
endianness = tinylink.BIG_ENDIAN
185185

186-
# Open serial port and create link
186+
# Open serial port and create link.
187187
handle = serial.Serial(arguments.port, baudrate=arguments.baudrate)
188188
link = tinylink.TinyLink(
189189
handle, max_length=arguments.length, endianness=endianness)
190190

191-
# Loop until finished
191+
# Loop until finished.
192192
try:
193-
# Input indicator
193+
# Input indicator.
194194
sys.stdout.write("--> ")
195195
sys.stdout.flush()
196196

197197
while True:
198198
readables, _, _ = select.select([handle, sys.stdin], [], [])
199199

200-
# Read from serial port
200+
# Read from serial port.
201201
if handle in readables:
202202
process_link(link)
203203

204-
# Read from stdin
204+
# Read from stdin.
205205
if sys.stdin in readables:
206206
if process_stdin(link) is False:
207207
break
208208

209-
# Input indicator
209+
# Input indicator.
210210
sys.stdout.write("--> ")
211211
sys.stdout.flush()
212212
except KeyboardInterrupt:
213213
handle.close()
214214

215-
# Done
215+
# Done.
216216
return 0
217217

218-
# E.g. `python tinylink_cli.py /dev/tty.usbmodem1337 --baudrate 9600'
218+
# E.g. `python cli.py /dev/tty.usbmodem1337 --baudrate 9600`.
219219
if __name__ == "__main__":
220220
run()

tinylink/consts.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This can be anything, and is used to synchronize a frame.
2+
PREAMBLE = 0xAA55AA55
3+
4+
# Endianness.
5+
LITTLE_ENDIAN = "<"
6+
BIG_ENDIAN = ">"
7+
8+
# Protocol states.
9+
WAITING_FOR_PREAMBLE = 1
10+
WAITING_FOR_HEADER = 2
11+
WAITING_FOR_BODY = 3
12+
13+
# Message flags (reserved).
14+
FLAG_NONE = 0x00
15+
FLAG_RESET = 0x01
16+
FLAG_ERROR = 0x02
17+
FLAG_PRIORITY = 0x04
18+
19+
# Do not change these values!
20+
LEN_PREAMBLE = 4
21+
LEN_FLAGS = 2
22+
LEN_LENGTH = 2
23+
LEN_XOR = 1
24+
LEN_CRC = 4
25+
LEN_HEADER = LEN_FLAGS + LEN_LENGTH + LEN_XOR
26+
LEN_BODY = LEN_CRC

0 commit comments

Comments
 (0)