Go to the documentation of this file.00001 """
00002 frame.py
00003 
00004 By Paul Malmsten, 2010
00005 pmalmsten@gmail.com
00006 
00007 Represents an API frame for communicating with an XBee
00008 """
00009 import struct
00010 
00011 class APIFrame:
00012     """
00013     Represents a frame of data to be sent to or which was received 
00014     from an XBee device
00015     """
00016     
00017     START_BYTE = '\x7E'
00018     ESCAPE_BYTE = '\x7D'
00019     XON_BYTE = '\x11'
00020     XOFF_BYTE = '\x13'
00021     ESCAPE_BYTES = (START_BYTE, ESCAPE_BYTE, XON_BYTE, XOFF_BYTE)
00022     
00023     def __init__(self, data='', escaped=False):
00024         self.data = data
00025         self.raw_data = ''
00026         self.escaped = escaped
00027         self._unescape_next_byte = False
00028         
00029     def checksum(self):
00030         """
00031         checksum: None -> single checksum byte
00032         
00033         checksum adds all bytes of the binary, unescaped data in the 
00034         frame, saves the last byte of the result, and subtracts it from 
00035         0xFF. The final result is the checksum
00036         """
00037         total = 0
00038         
00039         
00040         for byte in self.data:
00041             total += ord(byte)
00042             
00043         
00044         total = total & 0xFF
00045         
00046         
00047         return chr(0xFF - total)
00048 
00049     def verify(self, chksum):
00050         """
00051         verify: 1 byte -> boolean
00052         
00053         verify checksums the frame, adds the expected checksum, and 
00054         determines whether the result is correct. The result should 
00055         be 0xFF.
00056         """
00057         total = 0
00058         
00059         
00060         for byte in self.data:
00061             total += ord(byte)
00062             
00063         
00064         total += ord(chksum)
00065         
00066         
00067         total &= 0xFF
00068         
00069         
00070         return total == 0xFF
00071 
00072     def len_bytes(self):
00073         """
00074         len_data: None -> (MSB, LSB) 16-bit integer length, two bytes
00075         
00076         len_bytes counts the number of bytes to be sent and encodes the 
00077         data length in two bytes, big-endian (most significant first).
00078         """
00079         count = len(self.data)
00080         return struct.pack("> h", count)
00081         
00082     def output(self):
00083         """
00084         output: None -> valid API frame (binary data)
00085         
00086         output will produce a valid API frame for transmission to an 
00087         XBee module.
00088         """
00089         
00090         
00091         
00092         data = self.len_bytes() + self.data + self.checksum()
00093 
00094         
00095         if self.escaped and len(self.raw_data) < 1:
00096             self.raw_data = APIFrame.escape(data)
00097 
00098         if self.escaped:
00099             data = self.raw_data
00100 
00101         
00102         return APIFrame.START_BYTE + data
00103 
00104     @staticmethod
00105     def escape(data):
00106         """
00107         escape: byte string -> byte string
00108 
00109         When a 'special' byte is encountered in the given data string,
00110         it is preceded by an escape byte and XORed with 0x20.
00111         """
00112 
00113         escaped_data = ""
00114         for byte in data:
00115             if byte in APIFrame.ESCAPE_BYTES:
00116                 escaped_data += APIFrame.ESCAPE_BYTE
00117                 escaped_data += chr(0x20 ^ ord(byte))
00118             else:
00119                 escaped_data += byte
00120         
00121         return escaped_data
00122 
00123     def fill(self, byte):
00124         """
00125         fill: byte -> None
00126 
00127         Adds the given raw byte to this APIFrame. If this APIFrame is marked
00128         as escaped and this byte is an escape byte, the next byte in a call
00129         to fill() will be unescaped.
00130         """
00131 
00132         if self._unescape_next_byte:
00133             byte = chr(ord(byte) ^ 0x20) 
00134             self._unescape_next_byte = False
00135         elif self.escaped and byte == APIFrame.ESCAPE_BYTE:
00136             self._unescape_next_byte = True
00137             return
00138 
00139         self.raw_data += byte
00140 
00141     def remaining_bytes(self):
00142         remaining = 3
00143 
00144         if len(self.raw_data) >= 3:
00145             
00146             raw_len = self.raw_data[1:3]
00147             data_len = struct.unpack("> h", raw_len)[0]
00148 
00149             remaining += data_len
00150 
00151             
00152             remaining += 1
00153 
00154         return remaining - len(self.raw_data)
00155         
00156     def parse(self):
00157         """
00158         parse: None -> None
00159         
00160         Given a valid API frame, parse extracts the data contained
00161         inside it and verifies it against its checksum
00162         """
00163         if len(self.raw_data) < 3:
00164             ValueError("parse() may only be called on a frame containing at least 3 bytes of raw data (see fill())")
00165 
00166         
00167         raw_len = self.raw_data[1:3]
00168         
00169         
00170         data_len = struct.unpack("> h", raw_len)[0]
00171         
00172         
00173         data = self.raw_data[3:3 + data_len]
00174         chksum = self.raw_data[-1]
00175 
00176         
00177         self.data = data
00178         if not self.verify(chksum):
00179             raise ValueError("Invalid checksum")