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"
0 commit comments